Fp-ts: How to lift?

Created on 15 Jul 2019  Â·  8Comments  Â·  Source: gcanti/fp-ts

📖 Documentation

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?

All 8 comments

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

steida picture steida  Â·  4Comments

amaurymartiny picture amaurymartiny  Â·  4Comments

bioball picture bioball  Â·  4Comments

Crashthatch picture Crashthatch  Â·  4Comments

FruitieX picture FruitieX  Â·  3Comments