Почему этот код с бесплатным интерпретатором монад компилируется?

Я пытаюсь понять свободные монады. Итак, с помощью руководств я написал игрушечный пример для игры, и теперь я не понимаю, почему он компилируется. Вот:

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

person Artem Malinko    schedule 20.11.2016    source источник


Ответы (1)


Ваш метод apply должен возвращать Option[A], где A определяется типом аргумента. То есть, если аргумент имеет тип Operation[Unit], результат также должен быть Option[Unit] и так далее.

Теперь ваше тело полностью придерживается этого контракта. Да, у вас есть случай, когда вы возвращаете Option[Unit] вместо общего Option[A], но вы делаете это только в том случае, если аргумент был экземпляром Print и, следовательно, Operation[Unit]. То есть вы возвращаете Option[Unit] только тогда, когда аргумент был Operation[Unit], поэтому контракт не нарушается. То же самое и с Read и String. Обратите внимание, что если бы вы вернули 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, поэтому он имеет свой собственный тип, а не тип Unit. Единственным значением типа Unit является ().

В остальном ситуация та же, что и выше: шаблон имеет тип Operation[String], поэтому результат должен быть Operation[String], а не Operation[Unit] (или Operation[Unit.type]).

person sepp2k    schedule 20.11.2016
comment
Ничего себе, scalac действительно умный. Спасибо. - person Artem Malinko; 20.11.2016
comment
Я уже принял ответ, но можете ли вы взглянуть на мое обновление? - person Artem Malinko; 20.11.2016
comment
Мой плохой, извините. Это работает, как вы грустно. Я удалил обновление. - person Artem Malinko; 20.11.2016
comment
Поскольку вы уже ответили на мое обновление, я пишу вам в ответ:) Еще раз извините и спасибо за четкий ответ. - person Artem Malinko; 20.11.2016