3

When using FP-TS I often end up with structs being inside a Task or an IO. I managed to solve the problem by writing a merge function and separate lift functions that make it work with Task or IO. See the included code example for a more detailed explanation. The code works but I'm wondering if the custom functions I wrote are already available in FP-TS in some shape or form.

import { deepStrictEqual as assertEqual } from 'assert'
import { IO, of, io, map } from 'fp-ts/lib/IO';
import { sequenceT } from 'fp-ts/lib/Apply';
import { pipe, tupled } from 'fp-ts/lib/function';

type Merge<A, B> = A & B
function merge<A, B>(a: A, b: B): Merge<A, B> {
  return {...a, ...b}
}

type Foo = { foo: 123 }
type Bar = { bar: 456 }
type FooBar = Merge<Foo, Bar>;

const foo: Foo = { foo: 123 }
const bar: Bar = { bar: 456 }
const fooBar: FooBar = merge(foo, bar)

type IOLift<A, AS extends Array<any>, B> = (a: IO<A>, ...as: { [I in keyof AS]: IO<AS[I]> }) => IO<B> 
function ioLift<A, AS extends Array<any>, B>(f: (a: A, ...as: AS) => B): IOLift<A, AS, B> {
  return (...a) => pipe(
    sequenceT(io)(...a),
    map(tupled(f))
  )
}

const ioMerge = ioLift(merge)

const ioFoo: IO<Foo> = of(foo)
const ioBar: IO<Bar> = of(bar)
const ioFooBar: IO<FooBar> = ioMerge(ioFoo, ioBar)

assertEqual(ioFooBar(), fooBar)
cyberixae
  • 843
  • 5
  • 15

1 Answers1

3

Merging structs of the same type can be achieved with a Monoid instance (see getStructMonoid), but you're looking to merge structs with different interfaces, and there's not a type class for that operation. However, Object.assign is equivalent to your merge function, except that it can merge up to four arguments (any more than that and you'll get an any in TypeScript).

As for ioLift, there's nothing equivalent to that in fp-ts, but your implementation of sequenceT + map is the standard way of applying a function to some arguments when they each happen to be wrapped in some applicative functor, standard for fp-ts, that is, given the limitations of TypeScript (see @gcanti's comment here about why there's not liftN function).

Given how trivial the sequenceT + map composition is, one could argue that it doesn't meet the Fairbairn Threshold, meaning it's easier to just combine sequenceT + map inline whenever you find yourself in this situation rather than writing a specialized utility liftWhatever for every applicative functor you come across.

Derrick Beining
  • 876
  • 8
  • 15