This Week in Cats: Kleisli
Kleisli. It’s got a funny name but it’s actually a simple concept. Suppose you have two functions that you want to execute together:
val computeSquare(i: Int): Int = i * i val formatResponse(i: Int): String = s"Your answer is $i" // One way of doing it is val sqrAns = computeSquare(3) val strAns = formatResponse(sqrAns) // strAns = "Your answer is 9" // or avoiding naming variable val strAns = formatResponse(computeSquare(3)). // strAns = "Your answer is 9"
Instead of needing to call these two functions over and over, either as individual lines or nested, you can easily create a new function from the two that always does both using function composition:
val formattedSquare = computeSquare andThen formatResponse // or val formattedSquare = formatResponse compose computeSquare
Naturally, the output of one function flows as the input of the next function. We tend to run into
problems through when our functions start to return monadic types like
val computeSquare(i: Int): Either[Throwable, Int] = Right(i * i) val formatResponse(i: Int): Either[Throwable, String] = Right(s"Your answer is $i")
The output type of
computeSquare no longer matches the input type of
formatResponse. We could
make them match but accepting an Either with one type being an error isn't a natural way to write
a function and makes other uses of that function cumbersome.
The solution: Kleisli
val computeSquare(i: Int): Either[Throwable, Int] = Right(i * i) val formatResponse(i: Int): Either[Throwable, String] = Right(s"Your answer is $i") val formattedSquare = Kleisli(computedSquare) andThen Kleisli(formatResponse) val ans = formattedSquare(3) // ans = Right("Your answer is 9")
The name isn't obvious but is nothing more than a way to compose when returned types are wrapped in another monadic type.