4

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

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)
  }
4

1 回答 1

3

您的apply方法应该返回由参数类型确定的位置Option[A]A也就是说,如果参数有 type Operation[Unit],结果也应该是 anOption[Unit]等等。

现在你的身体完美地遵守了这个契约。是的,您确实有返回 anOption[Unit]而不是 general的情况Option[A],但只有当参数是 的实例Print并因此是Operation[Unit]. 也就是说,只有Option[Unit]当参数为 an 时,您才返回 an Operation[Unit],因此合同不会被破坏。Read和也是如此String。请注意,如果您Option[Unit]在 for 的情况下返回 an Read,那将是一个错误,因为您现在将返回参数类型以外的类型。

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


关于您的更新:

case Print(s) => Some(s)

这里我们有一个类型的模式Operation[Unit](记住Printextends Operation[Unit]),所以我们应该得到一个类型的结果Option[Unit],但是Some(s)有类型Option[String]。所以这是类型不匹配。

case Read() => Some(Unit)

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

除此之外,情况与上述相同:模式具有 type Operation[String],因此结果应该是Operation[String],而不是Operation[Unit](或Operation[Unit.type])。

于 2016-11-20T14:08:00.680 回答