【发布时间】:2018-02-26 16:20:19
【问题描述】:
我正在Scalaz 中尝试使用Free monad,并尝试构建简单的解释器来解析和评估表达式,例如:
dec(inc(dec(dec(10)))
其中dec 表示递减,inc 表示递增。这是我得到的:
trait Interpreter[A]
case class V[A](a: A) extends Interpreter[A]
object Inc {
private[this] final val pattern = Pattern.compile("^inc\\((.*)\\)$")
def unapply(arg: String): Option[String] = {
val m = pattern.matcher(arg)
if(m.find()){
Some(m.group(1))
} else None
}
}
object Dec {
private[this] final val pattern = Pattern.compile("^dec\\((.*)\\)$")
def unapply(arg: String): Option[String] = {
val m = pattern.matcher(arg)
if(m.find()){
Some(m.group(1))
} else None
}
}
object Val {
def unapply(arg: String): Option[Int] =
if(arg.matches("^[0-9]+$")) Some(Integer.valueOf(arg))
else None
}
现在这就是我构建 AST 所需的全部内容。目前看起来如下:
def buildAst(expression: String): Free[Interpreter, Int] =
expression match {
case Inc(arg) => inc(buildAst(arg))
case Dec(arg) => dec(buildAst(arg))
case Val(arg) => value(arg)
}
private def inc(i: Free[Interpreter, Int]) = i.map(_ + 1)
private def dec(d: Free[Interpreter, Int]) = d.map(_ - 1)
private def value(v: Int): Free[Interpreter, Int] = Free.liftF(V(v))
现在在测试应用程序时:
object Test extends App{
val expression = "inc(dec(inc(inc(inc(dec(10))))))"
val naturalTransform = new (Interpreter ~> Id) {
override def apply[A](fa: Interpreter[A]): Id[A] = fa match {
case V(a) => a
}
}
println(buildAst(expression).foldMap(naturalTransform)) //prints 12
}
而且它工作得非常好(我不确定它是否是scalaz 风格)。
问题是提取器对象Inc、Dec、Val 感觉就像样板代码。有没有办法减少这样的代码重复。
如果支持的功能越来越多,这肯定会成为问题。
【问题讨论】:
-
解析字符串不是
Free的好用例。它旨在描述许多可以排序的操作(DB 调用、FS 操作),而无需定义其实现,例如无需更改实际代码即可轻松测试和在同步/异步执行之间切换 -
@OlegPyzhcov 在我的情况下,
V不会是Interpreter的唯一实现。将有另一个具有副作用(从数据库中检索)。这就是为什么我认为使用Free是个好主意。 -
整个问题似乎出在自上而下的解析器上,而不是
Free。case class V[A](a: A)是Interpreter特征中的 only 操作,可以用不同的解释有意义地替换。无需输入的单一操作:在某种程度上看起来像是一种相当不完整的嵌入式领域特定语言? -
@AndreyTyukin 仍然不清楚为什么
Free在这里没有用。你能解释一下我对Free的理解哪里错了吗?问题是case class V(a)可能被解释为数据库中的行数。因此它的价值事先是未知的。所以使用Free可能对测试有用(避免与数据库相关的副作用)。 -
@St.Antario 我没有声称
Free没有用。我刚刚指出,问题似乎是 95% 自上而下解析和 5% 关于Free。整个pattern-compile-pattern-matching-group-match-case的东西与Free无关,所以Free不是这里的样板的原因。此外,如果V(a)应该被解释为存储A类型对象信息的行数,那么V应该是case class V[A](rowIdx: Int) extends Interpreter[A],否则我不明白你为什么想要传入A类型的值以获取A。
标签: scala scalaz free-monad