Search code examples
scalascalazmonad-transformerswriter-monad

How to compose functions that return Writer[List[Int], Int]?


Suppose I have a few functions Int => Int composed with andThen:

val f1: Int => Int = _ + 1
val f2: Int => Int = _ + 2
val f3: Int => Int = _ + 3

val f = f1 andThen f2 andThen f3

Now I need to return also the intermediate results. So I can convert all those functions to Int => (List[Int], Int) where the list contains the arguments.

I can probably use Writer[List[Int], Int] of scalaz to represent the pair (List[Int], Int):

val fw1: Int => Writer[List[Int], Int] = x => f1(x).set(List(x))
val fw2: Int => Writer[List[Int], Int] = x => f2(x).set(List(x))
val fw3: Int => Writer[List[Int], Int] = x => f3(x).set(List(x))

In order to compose fw1, fw2, and fw3 I probably need to wrap them with Kleisli. However Kleisli(fw1) does not compile since Writer[List[Int], Int] is not a monad.

I guess that I probably need a monad transformer to make Writer[List[Int], Int] a monad but I don't know exactly how to do it. So, my question is: how to make Kleisli(fw1) compile with a monad transformer ?


Solution

  • Writer[List[Int], ?] does have a monad instance—this is just a case where scalac isn't able to see that without a little help. You can just use kleisliU, which is like Kleisli.apply but with some type inference help from Unapply (which is described here and in a number of other places):

    import scalaz._, Scalaz._, Kleisli.kleisliU
    
    val f1: Int => Int = _ + 1
    val f2: Int => Int = _ + 2
    val f3: Int => Int = _ + 3
    
    val fw1: Int => Writer[List[Int], Int] = x => f1(x).set(List(x))
    val fw2: Int => Writer[List[Int], Int] = x => f2(x).set(List(x))
    val fw3: Int => Writer[List[Int], Int] = x => f3(x).set(List(x))
    
    val f = kleisliU(fw1) andThen kleisliU(fw2) andThen kleisliU(fw1)
    

    And then:

    scala> f.run(10)
    res0: scalaz.WriterT[[+X]X,List[Int],Int] = WriterT((List(10, 11, 13),14))
    

    You could also provide explicit type parameters for Kleisli.apply or Kleisli.kleisli.