我试图理解自由的 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)
  }

最佳答案

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

现在你的 body 完美地遵守了那个契约。是的,您确实遇到过返回 Option[Unit] 而不是一般 Option[A] 的情况,但只有当参数是 Print 的实例并因此是 Operation[Unit] 时才这样做。也就是说,您只在参数是 Option[Unit] 时才返回 Operation[Unit] ,因此契约(Contract)不会被破坏。 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] 类型的结果,但 Some(s)Option[String] 类型。所以这是一个类型不匹配。
case Read() => Some(Unit)

首先 Unit 它是 Unit 类型的伴随对象,所以它有自己的类型,而不是类型 UnitUnit 类型的唯一值是 ()

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

关于scala - 为什么使用免费 monad 解释器编译这段代码?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40704496/

10-16 22:25