Переводчики для Свободной Монады - PullRequest
1 голос
/ 14 марта 2019

Я делал упражнение, чтобы попытаться реализовать базовый калькулятор с помощью 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 в моей программе, и я ожидаю, что не придется переписывать мою программу для разных переводчиков.

1 Ответ

2 голосов
/ 14 марта 2019

Итак, пара вещей.Обычно вы действительно хотите избежать 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
    }
  }

, либо связать возможность с такими операциями, как эта.в основном вы можете рассматривать случаи в вашем ADT как Add следующим образом: параметры класса 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) )

ТеперьЧто касается вашего варианта Option, я не совсем уверен, почему вы хотите интерпретировать здесь Option.если вы можете сделать F ~> Id для некоторого F, F ~> Option на самом деле просто Some применяется к первому естественному преобразованию.

...