scalamonadsscalazkleisli

Kleisli Arrow with Writer in Scala. Why doesn't it compile?


This is a followup to my previous question. Looks like I still did not get it. Now I am trying to compose functions that return Writer monad.

scala> val f = {x:Int => Writer("doing " + x + ";", x + 1)}
f: Int => scalaz.WriterT[scalaz.Id.Id,String,Int] = 

scala> Kleisli(f) >=> Kleisli(f)
:16: error: no type parameters for method apply: (f: A => M[B])scalaz.Kleisli[M,A,B] in object Kleisli exist so that it can be applied to arguments (Int => scalaz.WriterT[scalaz.Id.Id,String,Int])
 --- because ---
argument expression's type is not compatible with formal parameter type;
 found   : Int => scalaz.WriterT[scalaz.Id.Id,String,Int]
 required: ?A => ?M

              Kleisli(f) >=> Kleisli(f)

Why doesn't it compile ?


Solution

  • When the Scala compiler expects a type shaped like M[B] and you give it something like WriterT[Id, String, Int], it's unfortunately just not smart enough to figure out that you want to fix the first two type parameters and use the monad for WriterT[Id, String, _].

    There are a few possible ways to work around this limitation. The first is to define a type alias:

    type StringWriter[A] = WriterT[Id, String, A]
    

    Now you can provide explicit type parameters (in fact you could do this without the alias, but the type lambdas would make the line twice as long and ten times as unreadable):

    scala> Kleisli[StringWriter, Int, Int](f) >=> Kleisli[StringWriter, Int, Int](f)
    res0: scalaz.Kleisli[StringWriter,Int,Int] = Kleisli(<function1>)
    

    Scalaz now provides a nicer solution, though, via Miles Sabin's "unapply trick":

    val ff = Kleisli.kleisliU(f) >=> Kleisli.kleisliU(f)
    

    kleisliU is essentially just a nice version of Kleisli.apply that uses a new type class (named Unapply) behind the scenes to guide the type inference system to the right way to break apart the WriterT[Id, String, Int].