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下的脚本只是一个线性的命令序列.
即使在网上搜索了几个小时之后,我也无法找到任何示例,这些示例可以更好地了解以下问题:
Suspend(Op(Suspend(Op(......(Result(Op))..))
AST表示,因此是AST的表示?这个假设是对的吗?PS:请尽量坚持使用Scala/ScalaZ,因为Haskell还不是我的专业领域.
在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]
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)
有了这个,让我们从你的问题开始:
- 线性操作的序列(当然也是树)由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
仿函数的每个情况只有一个"洞"要填充,因此没有分支.
- 如何在免费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)
- 将控制结构包含到脚本中的一般方法是什么(如源代码中的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.scala
和MonadSyntax.scala
,有也whileM
和iterateWhile
.