我最近发现了一个名为简单解释器的小scala example使用monads :
object simpleInterpreter {
case class M[A](value: A) {
def bind[B](k: A => M[B]): M[B] = k(value)
def map[B](f: A => B): M[B] = bind(x => unitM(f(x)))
def flatMap[B](f: A => M[B]): M[B] = bind(f)
}
def unitM[A](a: A): M[A] = M(a)
def showM(m: M[Value]): String = m.value.toString();
type Name = String
trait Term;
case class Var(x: Name) extends Term
case class Con(n: int) extends Term
case class Add(l: Term, r: Term) extends Term
case class Lam(x: Name, body: Term) extends Term
case class App(fun: Term, arg: Term) extends Term
trait Value
case object Wrong extends Value {
override def toString() = "wrong"
}
case class Num(n: int) extends Value {
override def toString() = n.toString()
}
case class Fun(f: Value => M[Value]) extends Value {
override def toString() = "<function>"
}
type Environment = List[Pair[Name, Value]]
def lookup(x: Name, e: Environment): M[Value] = e match {
case List() => unitM(Wrong)
case Pair(y, b) :: e1 => if (x == y) unitM(b) else lookup(x, e1)
}
def add(a: Value, b: Value): M[Value] = Pair(a, b) match {
case Pair(Num(m), Num(n)) => unitM(Num(m + n))
case _ => unitM(Wrong)
}
def apply(a: Value, b: Value): M[Value] = a match {
case Fun(k) => k(b)
case _ => unitM(Wrong)
}
def interp(t: Term, e: Environment): M[Value] = t match {
case Var(x) => lookup(x, e)
case Con(n) => unitM(Num(n))
case Add(l, r) => for (val a <- interp(l, e);
val b <- interp(r, e);
val c <- add(a, b))
yield c
case Lam(x, t) => unitM(Fun(a => interp(t, Pair(x, a) :: e)))
case App(f, t) => for (val a <- interp(f, e);
val b <- interp(t, e);
val c <- apply(a, b))
yield c
}
def test(t: Term): String =
showM(interp(t, List()))
val term0 = App(Lam("x", Add(Var("x"), Var("x"))), Add(Con(10), Con(11)))
val term1 = App(Con(1), Con(2))
def main(args: Array[String]) {
println(test(term0))
println(test(term1))
}
}
这里的monadic计算的用途/优势是什么?事实上,M
只不过是一个身份单身。这只是介绍一个monadic语法的例子,还是有重要影响?
答案 0 :(得分:19)
以下是Phil Wadler的论文摘要: 当您以简单直接的方式编写解释器时,添加新功能时必须更改许多代码。例如,如果添加例外,则必须检查是否在可能评估表达式的任何位置引发异常,即使构造类似于 if 或 while 或函数电话,所以与例外无关。
如果以monadic风格编写解释器,只需更改monad即可添加新功能。您通常还会添加一些新的语法来支持该功能,但其余代码都不会更改。因此,monadic风格是一种使语言变化模块化的翻译方式。
示例:
要添加例外,请将monad更改为错误monad,为throw
和catch
添加新的语法和代码,并且不会更改其他任何代码。
要更改语言以使表达式的值为概率分布,而不仅仅是值,更改monad,并添加概率结构,例如“翻转有偏见的硬币” 。同样,旧代码都没有改变。 (这个很有趣;我done it myself。)
既然我告诉过你monadic计算的优点,我最好告诉你最大的劣势:你一次只能做一个有趣的功能。原因是,一般来说,你不能组成两个monad来制作一个新的monad。这不仅仅是一般的,而是您可能真正喜欢使用的monad。
如果您真的对制作模块化口译员感兴趣,您可以在其中轻松尝试语言功能的不同组合(而不仅仅是单个功能),您需要 monad变换器即可。盛亮,保罗胡达克和马克琼斯在Monad Transformers and Modular Interpreters上发表了一篇很好的论文。这是一个很好的阅读;我高度推荐它。
答案 1 :(得分:4)
使用monad使解析器/解释器可扩展。 This paper by Philip Wadler需要一些时间来阅读,但非常详细地探讨了这个想法。另请参阅Monadic Parsing in Haskell。