免费Monad的译员

时间:2019-03-14 06:27:44

标签: scala functional-programming monads scala-cats free-monad

我一直在练习,以使用Free Monad实现基本的计算器。 据我了解,免费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])
    }
  }

由于我的ADT结果类型是A:Expression2 [A],所以我希望与Option解释器有关,而a和b vars为选项,而在字符串解释器a和b中则为字符串。 / p>

我也尝试使用Lit [A](a:A)代替Lit [A](a:Int),但后来失败了:当将A固定为a时,我无法为同一表达式传递不同的解释器Int程序中,我希望不必为其他解释器重写程序。

1 个答案:

答案 0 :(得分:2)

几件事。通常,您确实想避免使用asInstanceOf,因为现在您可以构造任何类型的Expression2,然后因为实际上不是Int而使评估崩溃。有两种方法可以减轻这种情况。您可以只在Expression2

中修复所包含数字类型的类型
import scalaz._
import Scalaz._


trait Expression2[A] extends Product with Serializable
case class Lit[A](a: Int) extends Expression2[Int]
case class Add[A](a: Int, b: Int) extends Expression2[Int]
case class Mult[A](a: Int, b: Int) extends Expression2[Int]

type ExprAlg[A] = Free[Expression2, A]

def lit(a: Int): ExprAlg[Int] = Free.liftF(Lit(a))
def add(a: Int, b: Int): ExprAlg[Int] = Free.liftF(Add(a, b))
def mult(a: Int, b: Int): ExprAlg[Int] = Free.liftF(Mult(a, b))

val eval: Expression2 ~> Id = new (Expression2 ~> Id) {
    override def apply[A](fa: Expression2[A]): Id[A] = eval(fa)

    def eval[A](expression2: Expression2[A]): A = expression2 match {
      case Lit(n) => n
      case Add(a, b) => a+b
      case Mult(a, b) => a*b
    }
  }

或者您可以将功能与此类操作相关联。基本上,您可以像这样Add这样来思考ADT中的案例:case类的参数就像函数参数一样,您放入Extends中的类型就是结果类型。

import scalaz._
import Scalaz._
import spire.algebra._
import spire.implicits._ 


trait Expression2[A] extends Product with Serializable
case class Lit[A](a: A) extends Expression2[A]
case class Add[A](a: A, b: A)(implicit val ev:Semiring[A]) extends Expression2[A]
case class Mult[A](a: A, b: A)(implicit val ev:Semiring[A]) extends Expression2[A]

type ExprAlg[A] = Free[Expression2, A]

def lit[A](a: A): ExprAlg[A] = Free.liftF(Lit(a))
def add[A](a: A, b: A)(implicit ev:Semiring[A]): ExprAlg[A] = Free.liftF(Add(a, b))
def mult[A](a: A, b: A)(implicit ev:Semiring[A]): ExprAlg[A] = Free.liftF(Mult(a, b))


val eval: Expression2 ~> Id = new (Expression2 ~> Id) {
  override def apply[A](fa: Expression2[A]): Id[A] = eval(fa)

  def eval[A](expression2: Expression2[A]): Id[A] = expression2 match {
    case Lit(n) =>  n
    case x:Add[A] => x.ev.plus(x.a,x.b)
    case x:Mult[A] => x.ev.times(x.a,x.b)
  }
}  

def program[A: Semiring](a:A,b:A) = for {
      two <- lit(a)
      four <- lit(b)
      sum <- add(two, four)
    } yield sum

println(program[Int](2,4).foldMap(eval) )

现在关于您的期权案,我不太确定您为什么要在这里解释为期权。如果您可以为某些F ~> IdF,那么F ~> Option实际上只是Some应用于第一个自然变换。