I\'m referring to the Ken Scambler\'s source code listed below, also see GitHub source .
package kenbot.free
import scalaz._
import Scalaz._
import Free._
impor
In Scalaz, the Free
monad as the two cases (simplified and ignoring the GoSub
optimization):
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]
Let's first see what Free.liftF
does, e.g. for
def get(key: String): Script[String] = liftF(Get(key, identity))
when doing get("key")
we will get
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))
Having that, let's start with your questions:
- The sequence of linear operations (which is also a tree of course) is represented by Suspend(Op(Suspend(Op(......(Result(Op))..)) and is thus a representation of the AST? Is this assumption right?
Essentially yes, a program written in the DSL using the free monad arising from a functor represents a chain of "steps" where each step is either a Suspend
containing one of your functor cases or a Return
representing the end of the chain.
As an concrete example, script
looks about like this:
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(())
))))))))))
As you can see, we essentially just "fill" the holes of our functor with the continuation of the computation, terminating with a Return
. In the sample DSL we will always have a linear chain, due to the fact that every case of the KVS
functor only has one "hole" to fill, so no branching.
- How is a real AST represented within the free monad? I assume, this happens, when control structures are included? (e.g. left and right tree branch, depending on condition) . Could someone please illustrate an example where real ASTs come into play? Maybe, an illustration of how an "if" could be implemented in the given example.
Let's extend our DSL with a branching construct:
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))
after changing the interpreter cases, it can be used like this:
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 ()
So now you have a "real" AST where I interpret your "real" as "has branching nodes" instead of the linear chain form as was the case up until now. Output is as expected:
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)
- What is the general approach to include control structures into scripts (as given under 5 in source code?)
First of all, remember that you can for example use the standard if inside 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 ()
Secondly, remember that due to the fact that Script[A]
is just Free[KVS,A]
you have a monad at disposal, so any "control structure" defined in e.g. Scalaz for monads will work for you too:
val script4: Script[Unit] = modify("key",_+"!").untilM_ { get("key").map(_.length > 42) }
println(interpretPure(script4, Map("key" -> "")))
// Map(key -> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!)
Have a look at Monad.scala
and MonadSyntax.scala
, there's also whileM
and iterateWhile
.