How can I compose two kleisli arrows(functions) f:A -> Promise B
and g: B -> Promise C
into h:A -> Promise C
using fp-ts?
I’m familiar with Haskell so I would ask in this way: What’s the equivalent of >=>
(fish operator)?
How can I compose two kleisli arrows(functions) f:A -> Promise B
and g: B -> Promise C
into h:A -> Promise C
using fp-ts?
I’m familiar with Haskell so I would ask in this way: What’s the equivalent of >=>
(fish operator)?
Promises are represented by Task
or TaskEither
monads in fp-ts
, which are asynchronous computations. TaskEither
additionally models failure and is the same as Task<Either<...>>
.
Kleisli Arrows can be composed via chain
operation of monads and flow
(pipe operator). The result resembles the application of >=>
operator in Haskell.
TaskEither
:
const f = (a: A): Promise<B> => Promise.resolve(42);
const g = (b: B): Promise<C> => Promise.resolve(true);
Convert functions returning Promise
to ones returning TaskEither
using tryCatchK
1:
import * as TE from "fp-ts/lib/TaskEither";
const fK = TE.tryCatchK(f, identity); // (a: A) => TE.TaskEither<unknown, B>
const gK = TE.tryCatchK(g, identity); // (b: B) => TE.TaskEither<unknown, C>
Compose both:
const piped = flow(fK, TE.chain(gK)); // (a: A) => TE.TaskEither<unknown, C>
Here is a copy paste block for Codesandbox:
// you could also write:
// import { taskEither as TE } from "fp-ts";
import * as TE from "fp-ts/lib/TaskEither";
// you could also write:
// import {pipeable as P} from "fp-ts"; P.pipe(...)
import { flow, identity, pipe } from "fp-ts/lib/function";
import * as T from "fp-ts/lib/Task";
type A = "A";
type B = "B";
type C = "C";
const f = (a: A): Promise<B> => Promise.resolve("B");
const g = (b: B): Promise<C> => Promise.resolve("C");
// Alternative to `identity`: use `toError` in fp-ts/lib/Either
const fK = TE.tryCatchK(f, identity);
const gK = TE.tryCatchK(g, identity);
const piped = flow(fK, TE.chain(gK));
const effect = pipe(
"A",
piped,
TE.fold(
(err) =>
T.fromIO(() => {
console.log(err);
}),
(c) =>
T.fromIO(() => {
console.log(c);
})
)
);
effect();
JavaScript Promises don't adhere to a monadic API, for example they are eagerly computed 2. In functional programming side effects are delayed as long as possible, so we need to use a compatible wrapper in form of Task
or TaskEither
.
1 identity
just forwards the error in failure case. You could also use toError
.
2 Incorporate monads and category theory #94 is worth reading, if you are interested in historical reasons.