为什么这个带有免费monad解释器的代码编译?

时间:2016-11-20 13:22:35

标签: scala free-monad

我试图理解免费的monad。因此,在教程的帮助下,我编写了玩具示例,现在我不明白为什么要编译。这是:

import cats.free.Free
import cats.instances.all._
import cats.~>

trait Operation[+A]

case class Print(s: String) extends Operation[Unit]

case class Read() extends Operation[String]


object Console {

  def print(s: String): Free[Operation, Unit] = Free.liftF(Print(s))

  def read: Free[Operation, String] = Free.liftF(Read())

}

object Interpreter extends (Operation ~> Option) {
  // why does this compile?
  override def apply[A](fa: Operation[A]): Option[A] = fa match {
    case Print(s) => Some(println(s))
    case Read() => Some(readLine())
  }
}

object Main {
  def main(args: Array[String]) {
    val program = for {
      _ <- Console.print("What is your name?")
      name <- Console.read
      _ <- Console.print(s"Nice to meet you $name")
    } yield ()
    program.foldMap(Interpreter)
  }
}

我正在谈论口译员的申请方法。它应该返回Option [A],但我可以在这里返回Option [Unit]和Option [String],所以我认为它应该是一个编译错误。但事实并非如此。这段代码编译和工作(虽然Idea告诉我它是一个错误)。那是为什么?

UPD:但为什么不编译?

def test[A](o: Operation[A]): Option[A] = o match {
    case Print(s) => Some(s)
    case Read() => Some(Unit)
  }

1 个答案:

答案 0 :(得分:3)

您的apply方法应返回Option[A],其中A由参数类型决定。也就是说,如果参数的类型为Operation[Unit],则结果也应为Option[Unit],依此类推。

现在你的身体完全符合合同。是的,您确实有一个返回Option[Unit]而不是一般Option[A]的情况,但只有在参数是Print的实例且因此Operation[Unit]时才会这样做}。也就是说,当参数为Option[Unit]时,您只返回Operation[Unit],因此合约不会被破坏。 ReadString也是如此。请注意,如果您在Option[Unit]的情况下返回Read,那将是一个错误,因为您现在将返回一个非参数类型的类型。

这就是为什么代码在语义上是正确的,但为什么要编译?这是因为Scala类型检查器(与IntelliJ的近似不同)足够聪明,可以在模式匹配时考虑其他类型信息。也就是说,在case Print中,它知道您刚刚将Operation[A]类型的值与类型Operation[Unit]的模式匹配,因此它会在案例的正文中指定A = Unit

关于您的更新:

case Print(s) => Some(s)

此处我们有Operation[Unit]类型的模式(请记住Print扩展Operation[Unit]),因此我们应该得到Option[Unit]类型的结果,但是{{1}有类型Some(s)。所以这是一种类型不匹配。

Option[String]

首先case Read() => Some(Unit) 它是Unit类型的伴随对象,因此它有自己的类型,而不是类型Unit。类型Unit的唯一值是Unit

除此之外,它与上述情况相同:模式的类型为(),因此结果应为Operation[String],而不是Operation[String](或Operation[Unit])。