This question isn’t meant as flame-bait! As it might be apparent, I’ve been looking at Scalaz recently. I’m trying to understand why I need some of the functionality that the library provides. Here’s something:
import scalaz._
import Scalaz._
type NEL[A] = NonEmptyList[A]
val NEL = NonEmptyList
I put some println statements in my functions to see what was going on (aside: what would I have done if I was trying to avoid side effects like that?). My functions are:
val f: NEL[Int] => String = (l: NEL[Int]) => {println("f: " + l); l.toString |+| "X" }
val g: NEL[String] => BigInt = (l: NEL[String]) => {println("g: " + l); BigInt(l.map(_.length).sum) }
Then I combine them via a cokleisli and pass in a NEL[Int]
val k = cokleisli(f) =>= cokleisli(g)
println("RES: " + k( NEL(1, 2, 3) ))
What does this print?
f: NonEmptyList(1, 2, 3)
f: NonEmptyList(2, 3)
f: NonEmptyList(3)
g: NonEmptyList(NonEmptyList(1, 2, 3)X, NonEmptyList(2, 3)X, NonEmptyList(3)X)
RES: 57
The RES value is the character count of the (String) elements in the final NEL. Two things occur to me:
- How could I have known that my NEL was going to be reduced in this manner from the method signatures involved? (I wasn’t expecting the result at all)
- What is the point of this? Can a reasonably simple and easy-to-follow use case be distilled for me?
This question is a thinly-veiled plea for some lovely person like retronym to explain how this powerful library actually works.
To understand the result, you need to understand the
Comonad[NonEmptyList]instance.Comonad[W]essentially provides three functions (the actual interface in Scalaz is a little different, but this helps with explanation):So,
Comonadprovides an interface for some containerWthat has a distinguished “head” element (copure) and a way of exposing the inner structure of the container so that we get one container per element (cojoin), each with a given element at the head.The way that this is implemented for
NonEmptyListis thatcopurereturns the head of the list, andcojoinreturns a list of lists, with this list at the head and all tails of this list at the tail.Example (I’m shortening
NonEmptyListtoNel):The
=>=function is coKleisli composition. How would you compose two functionsf: W[A] => Bandg: W[B] => C, knowing nothing about them other than thatWis aComonad? The input type offand the output type ofgaren’t compatible. However, you canmap(f)to getW[W[A]] => W[B]and then compose that withg. Now, given aW[A], you cancojoinit to get theW[W[A]]to feed into that function. So, the only reasonable composition is a functionkthat does the following:So for your nonempty list: