scalar.Coyoneda.liftTF 的上下文边界

context bound of scalar.Coyoneda.liftTF

看完 Rúnar Bjarnason 发人深省的视频 "Composable application architecture with reasonably priced monads",我开始用 Scalaz 编写视频中提供的示例。在为 Interact App 实现基于编写器的解释器时,我感到有些惊讶。这是代码,

sealed trait Interact[A]

case class Ask(prompt: String) extends Interact[String]

case class Tell(msg: String) extends Interact[Unit]

type W[A] = scalaz.Writer[Vector[String], A]

object ConsoleW extends (Interact ~> W) {
  def apply[A](i: Interact[A]) = i match {
    case Ask(prompt) =>
      println(prompt)
      Writer(Vector(prompt), readLine)
    case Tell(msg) =>
      println(msg)
      Writer(Vector.empty, ())
  }
}

当我试图将 ConsoleW 提升为 interact free monad 的解释器时,编译器提示我缺少 Functor 到 W 的上下文绑定。我很惊讶,因为 Writer 本身是一个 monad,functor 上下文绑定应该是免费提供。好的,所以我不得不明确地写一个仿函数,

implicit val functor = new Functor[W] {
  def map[A, B](fa: Writer[Vector[String], A])(f: A => B) =
    fa.map(f)
}

这很愚蠢...因为除了调用 Writer 本身的 map 方法外,我基本上什么也没做。现在我可以将 ConsoleW 提升为解释器。但是,当我尝试使用 ConsoleW 对交互程序进行 foldMap 时,编译器再次提示缺少绑定到 W 的 Monad 上下文!好吧,这完全出乎意料,让我用勺子喂你,Scalac...

val prg: Free[({type f[x] = Coyoneda[Interact, x]})#f, Unit] = ???

import scalaz.std.vector._

implicit val monad = new scalaz.Monad[W] {
  def point[A](a: => A) = Writer[Vector[String], A](Vector.empty, a)
  def bind[A, B](fa: Writer[Vector[String], A])(f: A => Writer[Vector[String], B]) = 
    fa.flatMap(f)
}

prg.foldMap(Coyoneda.liftTF(ConsoleW))

现在一切都编译通过了,Writer Monad可以在程序运行时将所有的提示记录到vector中。但这并不令人满意,我想创建一个以 Writer 作为基础 Monad 的解释器,而不必明确提供它确实是 Functor 和 Monad 的证据。知道这是怎么回事吗?

只需要导入scalaz.std.vector._,问题就解决了。我对 Id.Id 如何按原样工作感到印象深刻,忘记了 writer monad 的导入应该更复杂。