'What problem does the Kleisli arrows solve in fp-ts?

I'm learning functional programming by using fp-ts lib and noticed that some functions end with K, like:

  • chainTaskEitherK
  • chainEitherK
  • fromTaskK

Also I read the explanation of What a K suffix means in fp-ts documentation, but unfortunately I can't say that one example gives me a solid idea of how to use it on the battlefield.

I would like to know exactly what problem they solve and what the code would look like without them (to see the benefit).

Please, consider that I'm a newbie in this topic.



Solution 1:[1]

First, (going off of the example in the link you shared). I think a Kleisli arrow is just a name for a type that looks like:

<A>(value: A) => F<A>

where F is some Functor value. They call it a constructor in the doc you linked which might be more precise? My understanding is that it's just a function that takes some non-Functor value (a string in the parse example) and puts it into some Functor.

These helpers you've listed are there for when you already have a Kleisli arrow and you want to use it with some other Functor values. In the example they have

declare function parse(s: string): Either<Error, number>;

and they have an IOEither value that would probably come from user input. They want to combine the two, basically run parse on the input if it's a Right and end up with a function using parse with the signature:

declare function parseForIO(s: string): IOEither<Error, number>;

This is so the return type can be compatible with our input type (so we can use chain on the IOEither to compose our larger function).

fromEitherK is therefore, wrapping the base parse function in some logic to naturally transform the resulting regular Either into an IOEither. The chainEitherK does that and a chain to save some of the boilerplate.

Basically, it's solving a compatibility issue when the return value from your Kleisli arrows doesn't match the value you need when chaining things together.

Solution 2:[2]

In addition to the @Souperman explanation I want to share my investigation on this topic

Let's take already known example from the fp-ts documentation.

We have an input variable of type IOEtiher

const input: IE.IOEither<Error, string> = IE.right('foo')

and function, which take a plain string and returns E.Either

function parse(s: string): E.Either<Error, number> {
  // implentation
}

If we want to make this code works together in a fp-ts style, we need to introduce a pipe. pipe is a function which passes our data through the functions listed inside the pipe.

So, instead of doing this (imperative style)

const input: IE.IOEither<Error, string> = IE.right('foo')

const value = input()

let result: E.Either<Error, number>

if (value._tag === 'Right') {
  result = parse(value.right) // where value.right is our 'foo'
}

We can do this

pipe(
 input,
 IE.chain(inputValue => parse(inputValue))
                        ~~~~~~~~~~~~~~~~~ <- Error is shown
)

Error message

Type 'Either<Error, number>' is not assignable to type 'IOEither<Error, unknown>'.

Unfortunately, fp-ts cannot implicitly jump between types (e.g. from IOEither to Either) . In our example, we started with input variable which has IOEither (IE shortened) type and continue with a IE.chain method which tries to return Either value.

To make it work we can introduce a function which helps us to convert this types.

pipe(
 input,
 IE.chain(inputValue => IE.fromEitherK(parse)(inputValue))
)

Now our chain function explicitly know that parse function was converted from Either type to IOEither by using fromEitherK.

At this moment we can see that fromEitherK is a helper function that expects a Kleisli function in its arguments and return the new function with a new return type.

To make it more clear, we needn't to use a K suffix if, for example, our parse was a value (instead of function).

The code would look like

pipe(
 input,
 IE.chain(inputValue => IE.fromEither(parsed)) // I know this is useless code, but it shows its purpose
)

Returning back to our example. We can improve the code to make it more readable

Instead of this

pipe(
 input,
 IE.chain(inputValue => IE.fromEitherK(parse)(inputValue))
)

We can do this

pipe(
 input,
 IE.chain(IE.fromEitherK(parse))
)

And even more

pipe(
 input,
 IE.chainEitherK(parse)
)

Summary

As far I understand, a Kleisli arrows are the functions which take an argument and return a result wrapped in a container (like parse function).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Souperman
Solution 2