我指的是下面列出的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下的脚本只是一个线性的命令序列。
即使在网上搜索了几个小时之后,我也无法找到任何示例,这些示例可以更好地了解以下问题:
Suspend(Op(Suspend(Op(......(Result(Op))..))
表示,因此是AST的表示?这个假设是对的吗?P.S。:请尽量坚持使用Scala / ScalaZ,因为Haskell还不是我的专业领域。
答案 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))
有了这个,让我们从你的问题开始:
- 线性操作的序列(当然也是树)由Suspend(Op(Sus(((((((((((((((((((((( AST?这个假设是对的吗?
醇>
基本上是的,使用由仿函数产生的免费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(())
))))))))))
正如您所看到的,我们基本上只是通过计算的继续“填充”我们的仿函数的漏洞,以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))
更改解释器案例后,可以这样使用:
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)
- 将控制结构包含到脚本中的一般方法是什么(如源代码中的5所示?)
醇>
首先,请记住,例如,如果在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.scala
和MonadSyntax.scala
,还有whileM
和iterateWhile
。