Я делал упражнение, чтобы попытаться реализовать базовый калькулятор с помощью Free Monad.
Как я понимаю, намерение Свободной Монады и то, чего я хотел достичь, это:
напишите мою программу (математическое выражение), как только запустите ее с разными интерпретаторами.
Теперь я не уверен, что сделал 100% идиоматическую реализацию хотя бы потому что:
Моя программа должна быть параметризована для универсального типа A, который должен соответствовать контексту интерпретатора.
def program[A] = for {
two <- lit[A](2)
four <- lit[A](4)
sum <- add(two, four)
} yield sum
program[Int].foldMap(eval) shouldBe 6
program[String].foldMap(print) shouldBe "(2 + 4)"
import cats.instances.option._
program[Option[Int]].foldMap(evalOpt) shouldBe Option(6)
ADT / алгебра и «умные конструкторы»
trait Expression2[A] extends Product with Serializable
case class Lit[A](a: Int) extends Expression2[A]
case class Add[A](a: A, b: A) extends Expression2[A]
case class Mult[A](a: A, b: A) extends Expression2[A]
type ExprAlg[B] = Free[Expression2, B]
def lit[A](a: Int): ExprAlg[A] = Free.liftF(Lit(a))
def add[A](a: A, b: A): ExprAlg[A] = Free.liftF(Add(a, b))
def mult[A](a: A, b: A): ExprAlg[A] = Free.liftF(Mult(a, b))
Переводчик математики:
def eval: Expression2 ~> Id = new (Expression2 ~> Id) {
override def apply[A](fa: Expression2[A]): Id[A] = eval(fa).asInstanceOf[A]
def eval[A](expression2: Expression2[A]): Int = expression2 match {
case Lit(n) => n
case Add(a, b) => a.asInstanceOf[Int] + b.asInstanceOf[Int]
case Mult(a, b) => a.asInstanceOf[Int] * b.asInstanceOf[Int]
}
}
Переводчик печати:
def print: Expression2 ~> Id = new (Expression2 ~> Id) {
override def apply[A](fa: Expression2[A]): Id[A] = eval(fa).asInstanceOf[A]
def eval[A](expression2: Expression2[A]): String = expression2 match {
case Lit(n) => n.toString
case Add(a, b) => "(" + a.toString + " + " + b.toString + ")"
case Mult(a, b) => "(" + a.toString + " * " + b.toString + ")"
}
}
Математический переводчик в Option:
def evalOpt: Expression2 ~> Option = new (Expression2 ~> Option) {
override def apply[A](fa: Expression2[A]): Option[A] = eval(fa).map{_.asInstanceOf[A]}
def eval[A](expression2: Expression2[A]): Option[Int] = expression2 match {
case Lit(n) => Option(n)
case Add(a, b) => Option(a.asInstanceOf[Int] + b.asInstanceOf[Int])
case Mult(a, b) => Option(a.asInstanceOf[Int] * b.asInstanceOf[Int])
}
}
Относительно интерпретатора Option, я бы ожидал, что переменные a и b будут option, а в интерпретаторе строк a и b будут строки, поскольку мой тип результата ADT - A: Expression2 [A].
Я также попытался вместо Lit [A] (a: Int), чтобы Lit [A] (a: A), но затем он сломался: я не могу передать разные интерпретаторы для одного и того же выражения, когда A фиксируется на Int в моей программе, и я ожидаю, что не придется переписывать мою программу для разных переводчиков.