簡體   English   中英

在Scala貓中使用遞歸算術表達式的免費monad

[英]Free monads for recursive arithmetic expressions in Scala cats

我正在嘗試使用Scala cats庫定義一個簡單的算術表達式求值程序,並遵循教程doc

目標是以模塊化方式定義DSL,組合整數值和可以單獨定義的加法。

我有以下代碼:

package examples
import cats._
import cats.data._
import cats.free._
import cats.free.Free._
import scala.language.higherKinds

object FreeExpr {

  sealed trait ValueF[A]
  case class IntValue(n: Int) extends ValueF[Int]

  class Value[F[_]](implicit I: Inject[ValueF, F]) {
    def intVal(n:Int): Free[F,Int] = inject[ValueF,F](IntValue(n))
  }

  object Value {
    implicit def value[F[_]](implicit I: Inject[ValueF,F]): Value[F] = new Value[F]
  }

  sealed trait ArithF[A]
  case class Add[A](x: A, y: A) extends ArithF[A]

  class Arith[F[_]](implicit I: Inject[ArithF, F]) {
   def add[A](x: A, y: A) : Free[F,A] =
     Free.inject[ArithF,F](Add(x,y))
  }

  object Arith {
    implicit def arith[F[_]](implicit I: Inject[ArithF,F]): Arith[F] = new Arith[F]
  }

  type Expr[A] = Coproduct[ArithF, ValueF, A]
  type Result[A] = Id[A]

  object ArithId  extends (ArithF ~> Result) {
    def apply[A](fa: ArithF[A]) = fa match {
      case Add(x,y) => ??? // for { m <- x; n <- y } yield (m + n)
    }
  }

  object ValueId extends (ValueF ~> Result) {
    def apply[A](fa: ValueF[A]) = fa match {
      case IntValue(n) => Monad[Id].pure(n)
    }
  }

  val interpreter: Expr ~> Result = ArithId or ValueId

  def expr1(implicit value : Value[Expr],
                     arith : Arith[Expr]): Free[Expr, Int] = {
    import value._, arith._
    for {
      n <- intVal(2)
      m <- add(n, n)
    } yield m
   }

  lazy val run1 = expr1.foldMap(interpreter)

}

之前的代碼編譯是因為我評論了Add case的“apply”定義。 我最初認為解決方案是被注釋的代碼,但編譯器返回:

[error] ...FreeExpr.scala:40: value flatMap is not a member of type parameter A
[error]       case Add(x,y) => for { m <- x; n <- y } yield (m + n)
[error]                                   ^

您知道我需要在代碼中更改以模塊化方式定義DSL和解釋器嗎?

問題是Add的結果類型太籠統,在case Add(x,y) => ...case Add(x,y) => ...xy類型A而不是M[A]

我發現了兩種可能的解決方

一種是將Add的定義更改為case class Add(x:Int,y:Int)和解釋器的定義為

object ArithId  extends (ArithF ~> Result) {
    def apply[A](fa: ArithF[A]) = fa match {
      case Add(x,y) => x + y
    }
}

我已將此解決方案添加到此要點: FreeExprInt.scala

先前解決方案的一個問題是結果類型被固定為Int並且它可能更有趣,至少是Semigroup

我嘗試使用case class Add[A: Semigroup](x: A, y: A)來定義它case class Add[A: Semigroup](x: A, y: A)但似乎編譯器在處理第二個隱式參數列表時遇到了一些問題。

可能的解決方法是明確添加證據。 我將此解決方案添加到另一個要點: FreeExprExplicit.scala

雖然兩種解決方案都有效,但我對它們並不十分滿意,因為我更傾向於使用更通用的解決方案,其中結果類型可以是任何A

我被建議看看無標簽的最終樣式( GADT的替代品 ),但我還沒有實現它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM