Looks like people are quite happy with fp-ts-contrib's Do, it would be nice having a pipe-based Do though, here's a POC
import { pipe } from 'fp-ts/lib/function'
import { URIS2, Kind2 } from 'fp-ts/lib/HKT'
export interface Do2<M extends URIS2> {
readonly as: <N extends string>(name: N) => <E, A>(fa: Kind2<M, E, A>) => Kind2<M, E, { [K in N]: A }>
readonly let: <N extends string, A, B>(
name: Exclude<N, keyof A>,
f: (a: A) => B
) => <E>(fa: Kind2<M, E, A>) => Kind2<M, E, { [K in keyof A | N]: K extends keyof A ? A[K] : B }>
readonly bind: <N extends string, A, E, B>(
name: Exclude<N, keyof A>,
f: (a: A) => Kind2<M, E, B>
) => (fa: Kind2<M, E, A>) => Kind2<M, E, { [K in keyof A | N]: K extends keyof A ? A[K] : B }>
}
//
// example with `Either`
//
import * as E from 'fp-ts/lib/Either'
declare const Do: Do2<E.URI>
/*
const x: E.Either<string, {
a: number;
b: string;
c: boolean;
}>
*/
export const x = pipe(
E.right<string, number>(1),
Do.as('a'),
Do.bind('b', () => E.right('b')),
Do.let('c', ({ a, b }) => a + b.length > 0)
)
Pros:
do / doL are replaced by chainFirstreturn is mapdone is useless (would be the identity function)Looks good, but is bindW allowed?
Being pipe-based we (and users too) are free to add more functions (on data type basis)
export interface BindW2<M extends URIS2> {
readonly bindW: <N extends string, A, D, B>(
name: Exclude<N, keyof A>,
f: (a: A) => Kind2<M, D, B>
) => <E>(fa: Kind2<M, E, A>) => Kind2<M, D | E, { [K in keyof A | N]: K extends keyof A ? A[K] : B }>
}
declare const Do: Do2<E.URI> & BindW2<E.URI>
/*
const x: E.Either<string | number, {
a: number;
b: string;
c: boolean;
d: number;
}>
*/
export const x = pipe(
E.right<string, number>(1),
Do.as('a'),
Do.bind('b', () => E.right('b')),
Do.let('c', ({ a, b }) => a + b.length > 0),
Do.bindW('d', () => E.right<number, number>(2))
)
Love it! Where do I send the flowers? 馃尫 馃
Open question: as, bind, let, bindW etc... should be exported from Either.ts as a namespace (for example Do) or as top level functions?
Open question:
as,bind,let,bindWetc... should be exported fromEither.tsas a namespace (for exampleDo) or as top level functions?
@gcanti - If I understand correctly, you are asking about this
export const x = pipe(
E.right<string, number>(1),
E.as('a'),
E.bind('b', () => E.right('b')),
E.let('c', ({ a, b }) => a + b.length > 0),
E.bindW('d', () => E.right<number, number>(2))
)
versus this
export const x = pipe(
E.right<string, number>(1),
E.Do.as('a'),
E.Do.bind('b', () => E.right('b')),
E.Do.let('c', ({ a, b }) => a + b.length > 0),
E.Do.bindW('d', () => E.right<number, number>(2))
)
Am I correct?
If so, I would favor exporting as top-level functions since I find the former more readable (reduces clutter in the piped function calls). Just my preference though.
I would favor exporting as top-level functions
@IMax153 agree, I would rename as to bindTo though.
I'd start from adding
bindTobindbindW (when possible)to all monadic data types.
Hmm... I started experimenting with this and I get the following error when exporting let as a top-level function:
Identifier expected. 'let' is a reserved word in strict mode. Modules are automatically in strict mode.
Do you have a preference on another name for let?
@IMax153 PR here https://github.com/gcanti/fp-ts/pull/1283
Do you have a preference on another name for let?
Maybe we can live without it
export const x = pipe(
E.right<string, number>(1),
E.bindTo('a'),
E.bind('b', () => E.right('b')),
E.bind('c', ({ a, b }) => E.right(b.length > 0)), // <= this was a `let`
E.bindW('d', () => E.right<number, number>(2))
)