我正试图使用Scala-cats库并按照教程文档定义一个简单的算术表达式计算器。
目标是以模块化的方式定义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案例的"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) => ...
中,值x
和y
的类型为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)
定义它,但编译器在处理第二个隐式参数列表时似乎存在一些问题。
一个可能的解决方法是明确添加证据。我将这个解决方案添加到另一个要点中:FreeExprExplicit.scala
尽管这两种解决方案都有效,但我对它们并不太满意,因为我更喜欢一种更通用的解决方案,其中结果类型可以是任何A
。
有人建议我看看无标签的最终风格(GADT的替代品),但我还没有实现它。