自由monad与AST的关系

时间:2015-08-17 09:25:56

标签: scala monads abstract-syntax-tree

我指的是下面列出的Ken Scambler的源代码,另见GitHub source

package kenbot.free

import scalaz._
import Scalaz._
import Free._
import scala.collection.mutable

// This example is based off the one in Runar Bjarnason's "Dead Simple Dependency Injection" talk.
// http://www.youtube.com/watch?v=ZasXwtTRkio


// 0. Fantasy API
// def put(key: String, value: String): Unit
// def get(key: String): String
// def delete(key: String): Unit


// 1. ADT
sealed trait KVS[+Next]
case class Put[Next](key: String, value: String, next: Next) extends KVS[Next]     // <----  def put(key: String, value: String): Unit
case class Get[Next](key: String, onResult: String => Next) extends KVS[Next]      // <----  def get(key: String): String
case class Delete[Next](key: String, next: Next) extends KVS[Next]                 // <----  def delete(key: String): Unit


object KVS {
  type Script[A] = Free[KVS, A]

  // 2. Functor definition
  implicit val functor: Functor[KVS] = new Functor[KVS] {
    def map[A,B](kvs: KVS[A])(f: A => B): KVS[B] = kvs match {
      case Put(key, value, next) => Put(key, value, f(next))
      case Get(key, onResult) => Get(key, onResult andThen f)
      case Delete(key, next) => Delete(key, f(next))
    }
  }

  // 3. Lifting functions
  def put(key: String, value: String): Script[Unit] = liftF( Put(key, value, ()) )

  def get(key: String): Script[String] = liftF(Get(key, identity))

  def delete(key: String): Script[Unit] = liftF(Delete(key, ()))


  // 4. Composite functions
  def modify(key: String, f: String => String): Free[KVS, Unit] = for {
    v <- get(key)
    _ <- put(key, f(v))
  } yield ()


  // 5. Write scripts
  val script: Free[KVS, Unit] = for {
    id <- get("swiss-bank-account-id")
    _ <- modify(id, (_ + 1000000))
    _ <- put("bermuda-airport", "getaway car")
    _ <- delete("tax-records")
  } yield ()


  // 6. Interpreters

  // Building an immutable structure
  def interpretPure(kvs: Script[Unit], table: Map[String, String] = Map.empty): Map[String, String] = kvs.resume.fold({
    case Get(key, onResult) => interpretPure(onResult(table(key)), table)
    case Put(key, value, next) => interpretPure(next, table + (key -> value))
    case Delete(key, next) => interpretPure(next, table - key)
  }, _ => table)


  // Directly running effects
  def interpretImpure(kvs: Script[Unit], table: mutable.Map[String, String]): Unit = kvs.go {
    case Get(key, onResult) => onResult(table(key))
    case Put(key, value, next) => table += (key -> value); next
    case Delete(key, next) => table -= key; next
  }
}

如果遵循这些slides,任何脚本(参见源代码中的5.)都是&#34;转换&#34;在免费的monad中加入Suspend(Op(Suspend(Op(......(Result(Op))..))之类的东西。

不幸的是,5下的脚本只是一个线性的命令序列。

即使在网上搜索了几个小时之后,我也无法找到任何示例,这些示例可以更好地了解以下问题:

  1. 线性操作的顺序(当然也是树)由Suspend(Op(Suspend(Op(......(Result(Op))..))表示,因此是AST的表示?这个假设是对的吗?
  2. 如何在免费monad中代表真正的 AST?我想,当包含控制结构时会发生这种情况? (例如,左右树枝,视情况而定)。有人可以举一个真实的AST发挥作用的例子吗?也许,说明&#34; if&#34;可以在给定的例子中实现。
  3. 将控制结构包含到脚本中的一般方法是什么(如源代码中的5所示?)
  4. P.S。:请尽量坚持使用Scala / ScalaZ,因为Haskell还不是我的专业领域。

1 个答案:

答案 0 :(得分:5)

在Scalaz中,Free monad作为两个案例(简化并忽略了GoSub优化):

  sealed abstract class Free[S[_], A]
  case class Return[S[_], A](a: A) extends Free[S, A]
  case class Suspend[S[_], A](a: S[Free[S, A]]) extends Free[S, A]

让我们先看看Free.liftF做了什么,例如为了

def get(key: String): Script[String] = liftF(Get(key, identity))

在做get("key")时我们会得到

get("key")
// definition of get
liftF(Get("key",identity)
// definition of liftF
Suspend(Get("key",identity).map(Return)
// definition of map for Get above
Suspend(Get("key", identity andThen Return))
// identity andThen f == f
Suspend(Get("key", Return))

有了这个,让我们从你的问题开始:

  
      
  1. 线性操作的序列(当然也是树)由Suspend(Op(Sus(((((((((((((((((((((( AST?这个假设是对的吗?
  2.   

基本上是的,使用由仿函数产生的免费monad在DSL中编写的程序代表一系列“步骤”,其中每个步骤是包含一个仿函数案例的SuspendReturn代表链的末端。

作为一个具体的例子,script看起来像这样:

Suspend(Get("swiss-bank-account-id",
  id => Suspend(Get(id,
    v => Suspend(Put(id, v+1000000,
      _ => Suspend(Put("bermuda-airport","getaway car",
        _ => Suspend(Delete("tax-records",
          _ => Return(())
        ))))))))))

正如您所看到的,我们基本上只是通过计算的继续“填充”我们的仿函数的漏洞,以Return结束。在示例DSL中,我们将始终具有线性链,因为KVS仿函数的每个情况只有一个“洞”要填充,因此没有分支。

  
      
  1. 如何在免费monad中代表真正的AST?我想,当包含控制结构时会发生这种情况? (例如,左右树枝,视情况而定)。有人可以举一个真实的AST发挥作用的例子吗?也许,举例说明如何在给定的例子中实现“if”。
  2.   

让我们用分支结构扩展我们的DSL:

case class Cond[Next](cond: Boolean, ifTrue: Free[KVS,Next], ifFalse: Free[KVS,Next]) extends KVS[Next]
def cond[A](c: Boolean)(ifTrue: => Script[A])(ifFalse: => Script[A]): Script[A] =
    liftF(Cond(c,ifTrue,ifFalse))

更改解释器案例后,可以这样使用:

val script2: Script[Unit] = for {
  id <- get("swiss-bank-account-id")
  _ <- cond(id == "123") {
    Free.point[KVS,Unit](())
  } {
    for {
      _ <- modify(id, ("LOTS OF " + _))
      _ <- put("bermuda-airport", "getaway car")
      _ <- delete("tax-records")
    } yield ()
  }
} yield ()

所以现在你有了一个“真正的”AST,我将你的“真实”解释为“有分支节点”,而不是直到现在的情况下的线性链形式。输出符合预期:

println(interpretPure(
  script2,
  Map("swiss-bank-account-id" -> "42", "42" -> "money", "tax-records" -> "acme corp")))
// Map(swiss-bank-account-id -> 42, 42 -> LOTS OF money, bermuda-airport -> getaway car)

println(interpretPure(
  script2,
  Map("swiss-bank-account-id" -> "123", "tax-records" -> "acme corp")))
// Map(swiss-bank-account-id -> 123, tax-records -> acme corp)
  
      
  1. 将控制结构包含到脚本中的一般方法是什么(如源代码中的5所示?)
  2.   

首先,请记住,例如,如果在for-comprehensions中,你可以使用标准:

val script3: Script[Unit] = for {
  id <- get("swiss-bank-account-id")
  _ <- if (id == "123") {
    Free.point[KVS,Unit](())
  } else {
    for {
      _ <- modify(id, ("LOTS OF " + _))
      _ <- put("bermuda-airport", "getaway car")
      _ <- delete("tax-records")
    } yield ()
  }
} yield ()

其次,请记住,由于Script[A]只是Free[KVS,A],因此您可以使用monad,因此任何“控制结构”都可以在单调的Scalaz也适合你:

val script4: Script[Unit] = modify("key",_+"!").untilM_ { get("key").map(_.length > 42) }

println(interpretPure(script4, Map("key" -> "")))
// Map(key -> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!)

查看Monad.scalaMonadSyntax.scala,还有whileMiterateWhile