【问题标题】:What's the point of using monads in an interpreter?在解释器中使用 monad 有什么意义?
【发布时间】:2011-02-20 21:22:53
【问题描述】:

我最近发现了这个小scala example,叫做Simple interpreter using 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))
  }
}

这里一元计算的用途/优势是什么?事实上,M 只不过是一个身份单子。这只是为了给出一个单子语法的例子还是它有重要的作用?

【问题讨论】:

    标签: scala functional-programming monads


    【解决方案1】:

    以下是对 Phil Wadler 论文的一点总结: 当您以直截了当、“直接”的方式编写解释器时,在添加新功能时必须更改大量代码。例如,如果您添加异常,则必须检查是否在您可能评估表达式的任何地方引发异常,即使结构类似于 ifwhile 或函数调用,因此与异常无关。

    如果您以 monadic 风格编写解释器,则只需更改 monad 即可添加新功能。您通常还会添加一些新的语法来支持该功能,但其余代码都没有更改。因此,monadic 风格是一种制作解释器的方式,该解释器在语言变化方面是模块化的。

    例子:

    • 要添加异常,请将 monad 更改为错误 monad,为 throwcatch 添加新的语法和代码,其他代码均不更改。

    • 要更改语言以使表达式的值是一个概率分布,而不仅仅是一个值,请更改 monad,并添加一个概率构造,例如“flip a biased coin” .同样,没有任何旧代码更改。 (这个真的很有趣;我有done it myself。)

    既然我已经告诉你一元计算的优点,我最好告诉你最大的缺点:你一次只能做一个有趣的功能。原因是一般来说,你不能组合两个 monad 来创建一个新的 monad。这不仅适用于一般情况,也适用于您可能真正喜欢使用的 monad。

    如果您真的对制作模块化解释器感兴趣,您可以在其中轻松尝试不同的组合语言功能(而不仅仅是单个功能),您需要 monad 转换器。盛亮、Paul Hudak 和 Mark Jones 在Monad Transformers and Modular Interpreters 上有一篇很棒的论文。这是一本很棒的书;我强烈推荐它。

    【讨论】:

      【解决方案2】:

      使用 monad 使解析器/解释器具有可扩展性。 This paper by Philip Wadler 需要一些时间来阅读,但非常详细地探讨了这个想法。另见Monadic Parsing in Haskell

      【讨论】:

      • 感谢您的链接。但是您能否更具体地总结一下 monad 赋予了什么样的可扩展性?用例如替换身份绑定调试输出?解析器有什么实际用途。顺便说一句:上面的代码与单子解析器组合器无关(如您的 Haskell 示例所示)。
      • 你读过瓦德勒的论文吗?他给出了许多不同的例子,伐木就是其中之一。我知道你不是在解析,但 Wadler 的例子是在解释,我相信这就是你在做什么。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-07
      • 1970-01-01
      • 2018-07-08
      • 2017-08-04
      • 2020-08-31
      • 2015-06-13
      相关资源
      最近更新 更多