fp-ts icon indicating copy to clipboard operation
fp-ts copied to clipboard

sequenceS, but for single properties

Open DrTtnk opened this issue 2 years ago • 3 comments

🚀 Feature request

Current Behavior

None

Desired Behavior

I don't have the terminology, I'll try to be as clear as possible

I find myself with an object with a monad inside it, I need to expand the effect of such monad to the whole monad

Let's call it sequenceKey

const obj = {
      a: O.some(1),
      b: O.none,
      c: true,
    }
    
const res1 = sequenceKey("a", O.Applicative)(obj) // O.some({ a: 1, b: O.none, c: true })
const res2 = sequenceKey("b", O.Applicative)(obj) // O.none

Suggested Solution

Basically something like this

export function sequenceKey<P extends string, F extends URIS4>(
  prop: P, F: Apply4<F>
): <S, R, E, NER extends Record<P, Kind4<F, S, R, E, any>>>(
  r: NER & Record<P, Kind4<F, S, R, E, any>>
) => Kind4<F, S, R, E, { [K in keyof NER]: K extends P ? ([NER[K]] extends [Kind4<F, any, any, any, infer A>] ? A : never) : NER[K] }>
export function sequenceKey<P extends string, F extends URIS3>(
  prop: P, F: Apply3<F>
): <R, E, NER extends Record<P, Kind3<F, R, E, any>>>(
  r: NER & Record<P, Kind3<F, R, E, any>>
) => Kind3<F, R, E, { [K in keyof NER]: K extends P ? ([NER[K]] extends [Kind3<F, any, any, infer A>] ? A : never) : NER[K] }>
export function sequenceKey<P extends string, F extends URIS3, E>(
  prop: P, F: Apply3C<F, E>
): <R, NER extends Record<P, Kind3<F, R, E, any>>>(
  r: NER & Record<P, Kind3<F, R, E, any>>
) => Kind3<F, R, E, { [K in keyof NER]: K extends P ? ([NER[K]] extends [Kind3<F, any, any, infer A>] ? A : never) : NER[K] }>
export function sequenceKey<P extends string, F extends URIS2>(
  prop: P, F: Apply2<F>
): <E, NER extends Record<P, Kind2<F, E, any>>>(
  r: NER & Record<P, Kind2<F, E, any>>
) => Kind2<F, E, { [K in keyof NER]: K extends P ? ([NER[K]] extends [Kind2<F, any, infer A>] ? A : never) : NER[K] }>
export function sequenceKey<P extends string, F extends URIS2, E>(
  prop: P, F: Apply2C<F, E>
): <NER extends Record<P, Kind2<F, E, any>>>(
  r: NER
) => Kind2<F, E, { [K in keyof NER]: K extends P ? ([NER[K]] extends [Kind2<F, any, infer A>] ? A : never) : NER[K] }>
export function sequenceKey<P extends string, F extends URIS>(
  prop: P, F: Apply1<F>
): <NER extends Record<P, Kind<F, any>>>(
  r: NER
) => Kind<F, { [K in keyof NER]: K extends P ? ([NER[K]] extends [Kind<F, infer A>] ? A : never) : NER[K] }>
export function sequenceKey<P extends string, F>(
  prop: P, F: Apply<F>
): <NER extends Record<P, HKT<F, any>>>(
  r: NER
) => HKT<F, { [K in keyof NER]: K extends P ? ([NER[K]] extends [HKT<F, infer A>] ? A : never) : NER[K] }>

export function sequenceKey<P extends string, F>(prop: P, F: Apply<F>) {
  return (obj: Record<P, HKT<F, any>>) => F.map(obj[prop], (value) => ({ ...obj, [prop]: value }))
}

And the potential traverseKey, for which I still have to find the type

There is also a fork https://github.com/DrTtnk/fp-ts

Who does this impact? Who is this for?

From beginners and above, every time there are consideration to make for a whole object, given a single property

Describe alternatives you've considered

Using boilerplate code with pipe, map, object destructuring... That has to be reimplemented in case there were other monadic types to manage

const obj = {
  a: O.some(1),
  b: "a",
  c: O.none
}

const result = pipe(
  obj,
  ({a, ...base}) => pipe(
    a,
    O.map((a) => ({...base, a}))
  )
)

Your environment

Software Version(s)
fp-ts 2.21.1
TypeScript 4.7.4

DrTtnk avatar Sep 01 '22 11:09 DrTtnk

The code you have posted implies that there is some computation down the road where b and c depend on a. I think the do notation using apS and bindS works well for such cases and clearly shows the intent.

vvzz avatar Dec 25 '22 01:12 vvzz

I wish I could do something like this:

const test = {
  a: 'a',
  b: 'b',
  c: O.some('c'),
}

const res = pipe(
  O.some(test),
  O.bind('c', (c) => O.some(c)), 
  //     ^^^ TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
)

Unfortunately this is a typeerror

DrTtnk avatar Dec 25 '22 10:12 DrTtnk

In that case, what about sequenceS(O.Applicative)({a:O.some(obj.a), b:O.some(obj.b),c})

Ultimately, that data structure has to go through some sort of transformation like that since as it stands(and I assume thats due to upstream api or whatever) it is sort of smelly. I think values in a product type should not have dependency on each other like this.

vvzz avatar Dec 25 '22 18:12 vvzz