The FAQ says:
Where do I find the liftA2, liftA3, … functions?
Use sequenceT and sequenceS instead.
But I'm not sure how that translates... for example, this doesn't work:
const add = (a:number) => (b:number) => a + b;
sequenceT (O.option) (add, O.some(2), O.some(3)); //would like to get a O.some(5) here...
Any tips on how to lift a function over a couple Options?
pipe(
sequenceT(O.option)(O.some(2), O.some(3)),
O.map(([a, b]) => add(a)(b))
)
Thanks - that works great :)
I tried making it generic so I could pass O.option in, but the required signature confused me :
Would be nice to have liftN built-in as an abstraction on top of sequence+map
Then I would recommend to use O.option.map instead of the pipeable operator.
Sorry... not following. What I mean is instead of this:
export const liftOption2 = <A,B,OUT>
(f:(a:A) => (b:B) => OUT) =>
(a:Option<A>) =>
(b:Option<B>):Option<OUT> =>
pipe(
sequenceT (O.option) (a, b),
O.map(([a, b]) => f (a) (b))
);
Something like:
export const liftA2 = <F,A,B,OUT>
(F:F) =>
(f:(a:A) => (b:B) => OUT) =>
(a:F<A>) =>
(b:F<B>):F<OUT> =>
pipe(
sequenceT (F) (a, b),
F.map(([a, b]) => f (a) (b))
);
export const liftOption2 = liftA2 (O.option);
Though that's broken in a few ways (missing constraints on F, can't know what F.map is, etc.)
Would be nice to have liftN built-in
@dakom AFAIK is not possible, it wouldn't play well with polymorphic functions
Definition
import { Apply, Apply1 } from 'fp-ts/lib/Apply'
import { HKT, Kind, URIS } from 'fp-ts/lib/HKT'
function liftA2<F extends URIS>(
F: Apply1<F>
): <A, B, C>(f: (a: A) => (b: B) => C) => (fa: Kind<F, A>) => (fb: Kind<F, B>) => Kind<F, C>
function liftA2<F>(
F: Apply<F>
): <A, B, C>(f: (a: A) => (b: B) => C) => (fa: HKT<F, A>) => (fb: HKT<F, B>) => HKT<F, C> {
return f => fa => fb => F.ap(F.map(fa, f), fb)
}
Usage
import * as O from 'fp-ts/lib/Option'
// monomorphic functions...
const add = (a: number) => (b: number) => a + b
// ...are ok, liftedAdd: (fa: O.Option<number>) => (fb: O.Option<number>) => O.Option<number>
const liftedAdd = liftA2(O.option)(add)
// however if we try with a polymorphic function...
const tuple = <A>(a: A) => <B>(b: B): [A, B] => [a, b]
// liftedTuple: <A>(fa: O.Option<A>) => (fb: O.Option<unknown>) => O.Option<[A, unknown]>
const liftedTuple = liftA2(O.option)(tuple)
Even a specialized liftA2Option doesn't play well with with polymorphic functions
declare function liftA2Option<A, B, C>(
f: (a: A) => (b: B) => C
): (fa: O.Option<A>) => (fb: O.Option<B>) => O.Option<C>
// liftedTuple2: <A>(fa: O.Option<A>) => (fb: O.Option<unknown>) => O.Option<[A, unknown]>
const liftedTuple2 = liftA2Option(tuple)
interesting... can't say I follow _completely_ but I'm sure this answers the issue :)
Fairly certain that this response in the issue is related: https://github.com/microsoft/TypeScript/issues/32041#issuecomment-504780504
It seems that if I move generic B in tuple function higher in scope like so (which is not desired):
const tuple = <A, B>(a: A) => (b: B): [A, B] => [a, b];
Then liftedTuple has the type
<A, B>(fa: O.Option<A>) => (fb: O.Option<B>) => O.Option<[A, B]>
If, I move that generic back near returned function, then it gets erased in the derived definition of liftedTuple and gets replaced with resolved value unknown.
I think this issue can safely be closed. @dakom feel free to reopen if needed