自由monad与AST的关系

Mar*_*nne 6 monads scala abstract-syntax-tree

我指的是下面列出的Ken Scambler的源代码,也参见GitHub源代码.

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
  }
}
Run Code Online (Sandbox Code Playgroud)

如果按照这些幻灯片,任何脚本(参见源代码中的5.)都会被"转换"为类似于Suspend(Op(Suspend(Op(......(Result(Op))..))免费monad中的内容.

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

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

  1. 线性操作的顺序(当然也是树)由Suspend(Op(Suspend(Op(......(Result(Op))..))AST表示,因此是AST的表示?这个假设是对的吗?
  2. 如何在免费monad中代表真正的 AST?我想,当包含控制结构时会发生这种情况?(例如,左右树枝,视情况而定).有人可以举一个真实的AST发挥作用的例子吗?也许,举例说明如何在给定的例子中实现"if".
  3. 将控制结构包含到脚本中的一般方法是什么(如源代码中的5所示?)

PS:请尽量坚持使用Scala/ScalaZ,因为Haskell还不是我的专业领域.

Mar*_*189 5

在Scalaz中,Freemonad作为两个案例(简化并忽略了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]
Run Code Online (Sandbox Code Playgroud)

让我们先看看有什么用Free.liftF,例如

def get(key: String): Script[String] = liftF(Get(key, identity))
Run Code Online (Sandbox Code Playgroud)

什么时候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))
Run Code Online (Sandbox Code Playgroud)

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

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

基本上是的,使用由仿函数产生的免费monad在DSL中编写的程序代表一系列"步骤",其中每个步骤要么Suspend包含一个函子案例,要么Return代表链的末尾.

作为一个具体的例子,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(())
        ))))))))))
Run Code Online (Sandbox Code Playgroud)

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

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

让我们用分支结构扩展我们的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))
Run Code Online (Sandbox Code Playgroud)

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

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 ()
Run Code Online (Sandbox Code Playgroud)

所以现在你有了一个"真正的"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)
Run Code Online (Sandbox Code Playgroud)
  1. 将控制结构包含到脚本中的一般方法是什么(如源代码中的5所示?)

首先,请记住,例如,如果内部为了理解,您可以使用标准:

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 ()
Run Code Online (Sandbox Code Playgroud)

其次,请记住,由于这样的事实,Script[A]就是Free[KVS,A]你必须在处置一个单子,所以在如Scalaz定义的任何单子"控制结构"会为你工作了:

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

println(interpretPure(script4, Map("key" -> "")))
// Map(key -> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!)
Run Code Online (Sandbox Code Playgroud)

看看Monad.scalaMonadSyntax.scala,有也whileMiterateWhile.