在上节我们介绍了Free Monad的基本情况。可以说Free Monad又是一个以数据结构替换程序堆栈的实例。实际上Free Monad的功能绝对不止如此,以heap换stack必须成为Free Monad的运算模式,这样我们才可以放心的使用Free Monad所产生的Monadic编程语言了。前面我们介绍了Trampoline的运算模式可以有效解决堆栈溢出问题,而上节的Free Monad介绍里还没有把Free Monad与Trampoline运算模式挂上钩。我们先考虑一下如何在Free Monad数据类型里引入Trampoline运算模式。
我们先对比一下Tranpoline和Free这两个数据类型的基本结构:
trait Free[F[_],A] {
private case class FlatMap[B](a: Free[F,A], f:A => Free[F,B]) extends Free[F,B]
def unit(a: A) = Return(a)
def flatMap[B](f: A => Free[F,B])(implicit F: Functor[F]): Free[F,B] = this match {
case Return(a) => f(a)
case Suspend(k) => Suspend(F.map(k)( _ flatMap f))
case FlatMap(b,g) => FlatMap(b, x => g(x) flatMap f) //FlatMap(b, g andThen (_ flatMap f))
}
def map[B](f: A => B)(implicit F: Functor[F]): Free[F,B] = flatMap(a => Return(f(a)))
}
case class Return[F[_],A](a: A) extends Free[F,A]
case class Suspend[F[_],A](ffa: F[Free[F,A]]) extends Free[F,A]
trait Trampoline[A] {
private case class FlatMap[B](a: Trampoline[A], f: A => Trampoline[B]) extends Trampoline[B]
final def runT: A = resume match {
case Right(a) => a
case Left(k) => k().runT
}
def unit[A](a: A) = Done(a)
def flatMap[B](f: A => Trampoline[B]): Trampoline[B] = this match {
// case FlatMap(b,g) => FlatMap(b, g andThen (_ flatMap f)
// case FlatMap(b,g) => FlatMap(b, x => FlatMap(g(x),f))
case FlatMap(b,g) => FlatMap(b, x => g(x) flatMap f)
case x => FlatMap(x,f)
}
def map[B](f: A => B): Trampoline[B] = flatMap(a => More(() => Done(f(a))))
final def resume: Either[() => Trampoline[A],A] = this match {
case Done(a) => Right(a)
case More(k) => Left(k)
case FlatMap(a,f) => a match {
case Done(v) => f(v).resume
case More(k) => Left(() => FlatMap(k(),f))
case FlatMap(b,g) => FlatMap(b, g andThen (_ flatMap f)).resume
}
}
}
case class Done[A](a: A) extends Trampoline[A]
case class More[A](k: () => Trampoline[A]) extends Trampoline[A]
这两个数据类型的设计目的都是为了能逐步运行算法:按照算法运算的状态确定下一步该如何运行。这个F[Free[F,A]]就是一个循环递归结构,里面保存了运算当前状态和下一步运算。
我们曾说如果一个数据类型能有个Functor实例,那么我们就可以用它来产生一个Free Monad。这个要求从上面Free[F,A]类型里的map,flatMap可以了解:我们用了implicit F: Functor[F]参数,因为必须有个Functor实例F才能实现map和flatMap。
为了实现Free Monad在运行中采用Trampoline运行机制,我们可以像Trampoline数据类型一样来实现resume,这个确定每一步运算方式的函数:
trait Free[F[_],A] {
private case class FlatMap[B](a: Free[F,A], f:A => Free[F,B]) extends Free[F,B]
def unit(a: A) = Return(a)
def flatMap[B](f: A => Free[F,B])(implicit F: Functor[F]): Free[F,B] = this match {
case Return(a) => f(a)
case Suspend(k) => Suspend(F.map(k)( _ flatMap f))
case FlatMap(b,g) => FlatMap(b, x => g(x) flatMap f) //FlatMap(b, g andThen (_ flatMap f))
}
def map[B](f: A => B)(implicit F: Functor[F]): Free[F,B] = flatMap(a => Return(f(a)))
final def resume(implicit F: Functor[F]): Either[F[Free[F,A]],A] = this match {
case Return(a) => Right(a)
case Suspend(k) => Left(k)
case FlatMap(a,f) => a match {
case Return(v) => f(v).resume
case Suspend(k) => Left(F.map(k)(_ flatMap f))
case FlatMap(b,g) => FlatMap(b, g andThen (_ flatMap f)).resume
}
}
}
case class Return[F[_],A](a: A) extends Free[F,A]
case class Suspend[F[_],A](ffa: F[Free[F,A]]) extends Free[F,A]
我们用个实际例子来体验一下用Functor产生Free:
我们可以用上一节的Interact类型:
trait Interact[A]
case class Ask(prompt: String) extends Interact[String]
case class Tell(msg: String) extends Interact[Unit]
trait Interact[A]
case class Ask[A](prompt: String, next: A) extends Interact[A]
case class Tell[A](msg: String, next: A) extends Interact[A]
trait Interact[A]
case class Ask[A](prompt: String, next: A) extends Interact[A]
case class Tell[A](msg: String, next: A) extends Interact[A]
implicit val interactFunctor = new Functor[Interact] {
def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ia match {
case Ask(x,n) => Ask(x,f(n))
case Tell(x,n) => Tell(x,f(n))
}
} //> interactFunctor : ch13.ex1.Functor[ch13.ex1.Interact] = ch13.ex1$$anonfun$
接下来我们要把Interact类型升格到Free类型:
def liftF[F[_],A](fa: F[A])(implicit F: Functor[F]): Free[F,A] = {
Suspend(F.map(fa)(a => Return(a)))
} //> liftF: [F[_], A](fa: F[A])(implicit F: ch13.ex1.Functor[F])ch13.ex1.Free[F,
//| A]
implicit def LiftInteract[A](ia: Interact[A]): Free[Interact,A] = liftF(ia)
//> LiftInteract: [A](ia: ch13.ex1.Interact[A])ch13.ex1.Free[ch13.ex1.Interact,
//| A]
val prg = for {
first <- Ask("What's your first name?",())
last <- Ask("What's your last name?",())
_ <- Tell(s"Hello $first $last",())
} yield () //> prg : ch13.ex1.Free[ch13.ex1.Interact,Unit] = Suspend(Ask(What's your firs
//| t name?,Suspend(Ask(What's your last name?,Suspend(Tell(Hello () (),Return(
//| ())))))))
还是那句话:用一个有Functor实例的类型就可以产生一个Free Monad。然后我们可以用这个产生的Monad来在for-comprehension里面编写一个算法。
解译运算(Interpret)是Free Monad的Interpreter功能。我们说过要把Trampoline运行机制引入Free Monad运算:
def foldMap[G[_]](f: F ~> G)(implicit F: Functor[F], G: Monad[G]): G[A] = resume match {
case Right(a) => G.unit(a)
case Left(k) => G.flatMap(f(k))(_ foldMap f)
}
前面介绍的Free Monad相对都比较简单。实际上Free Monad的Suspend处理可以是很复杂的,包括返回结果及接受输入等任何组合。下面我们再看一个较复杂的例子:我们可以把State视为一种简单的状态转变编程语言,包括读取及设定状态两种操作指令:
trait StateF[S,A]
case class Get[S,A](f: S => A) extends StateF[S,A]
case class Put[S,A](s: S, a: A) extends StateF[S,A]
implicit def stateFFunctor[S] = new Functor[({type l[x] = StateF[S,x]})#l] {
def map[A,B](sa: StateF[S,A])(f: A => B): StateF[S,B] = sa match {
case Get(g) => Get( s => f(g(s)) )
case Put(s,a) => Put(s, f(a))
}
} //> stateFFunctor: [S]=> ch13.ex1.Functor[[x]ch13.ex1.StateF[S,x]]
type FreeState[S,A] = Free[({type l[x] = StateF[S,x]})#l, A]
现在我们已经得到了一个FreeState Monad。下面接着实现FreeState的基础组件函数:
def unit[S,A](a: A): FreeState[S,A] = Return[({type l[x] = StateF[S,x]})#l, A](a)
//> unit: [S, A](a: A)ch13.ex1.FreeState[S,A]
def getState[S]: FreeState[S,S] = Suspend[({type l[x] = StateF[S,x]})#l, S](
Get(s => Return[({type l[x] = StateF[S,x]})#l, S](s)))
//> getState: [S]=> ch13.ex1.FreeState[S,S]
def setState[S](s: S): FreeState[S,Unit] = Suspend[({type l[x] = StateF[S,x]})#l, Unit](
Put(s, Return[({type l[x] = StateF[S,x]})#l, Unit](())))
//> setState: [S](s: S)ch13.ex1.FreeState[S,Unit]
def evalS[S,A](s: S, t: FreeState[S,A]): A = t.resume match {
case Right(a) => a
case Left(Get(f)) => evalS(s, f(s))
case Left(Put(n,a)) => evalS(n,a)
} //> evalS: [S, A](s: S, t: ch13.ex1.FreeState[S,A])A
这个运算方式还是调用了resume函数。注意:Get(f) 返回 StateF[S,A],StateF是个Functor, F[Free[F,A]]那么A就是Free[F,A]
还是试试运算那个zipIndex函数:
def zipIndex[A](as: List[A]): List[(Int, A)] = {
evalS(1, as.foldLeft(unit[Int,List[(Int,A)]](List()))(
(acc,a) => for {
xs <- acc
n <- getState
_ <- setState(n+1)
} yield (n, a) :: xs)).reverse
} //> zipIndex: [A](as: List[A])List[(Int, A)]
zipIndex((0 to 10000).toList) //> res0: List[(Int, Int)] = List((1,0), (2,1), (3,2), (4,3), (5,4), (6,5), (7,
//| 6), (8,7), (9,8), (10,9), (11,10), (12,11), (13,12), (14,13), (15,14), (16,
//| 15), (17,16), (18,17), (19,18), (20,19), (21,20), (22,21), (23,22), (24,23)
没错,这段程序不但维护了一个状态而且使用了Trampoline运算模式,可以避免StackOverflow问题。
下面我们再用一个例子来示范Free Monad的Monadic Program和Interpreter各自的用途:
我们用一个Stack操作的例子。对Stack中元素的操作包括:Push,Add,Sub,Mul,End。这几项操作也可被视作一种Stack编程语言中的各项操作指令:
trait StackOps[A]
case class Push[A](value: Int, ops:A) extends StackOps[A]
case class Add[A](ops: A) extends StackOps[A]
case class Mul[A](ops: A) extends StackOps[A]
case class Dup[A](ops: A) extends StackOps[A]
case class End[A](ops: A) extends StackOps[A]
implicit val stackOpsFunctor: Functor[StackOps] = new Functor[StackOps] {
def map[A,B](oa: StackOps[A])(f: A => B): StackOps[B] = oa match {
case Push(v,a) => Push(v,f(a))
case Add(a) => Add(f(a))
case Mul(a) => Mul(f(a))
case Dup(a) => Dup(f(a))
case End(a) => End(f(a))
}
}
这里的next看起来是多余的,但它代表的是下一步运算。有了它才可能得到Functor实例,即使目前每一个操作都是完整独立步骤。
有了Functor实例我们就可以实现StackOps的Monadic programming:
def liftF[F[_],A](fa: F[A])(implicit F: Functor[F]): Free[F,A] = {
Suspend(F.map(fa)(a => Return(a)))
} //> liftF: [F[_], A](fa: F[A])(implicit F: ch13.ex1.Functor[F])ch13.ex1.Free[F,
//| A]
implicit def liftStackOps[A](sa: StackOps[A]): Free[StackOps,A] = liftF(sa)
//> liftStackOps: [A](sa: ch13.ex1.StackOps[A])ch13.ex1.Free[ch13.ex1.StackOps,
//| A]
val stkprg = for {
_ <- Push(1,())
_ <- Push(2,())
_ <- Add(())
} yield x //> stkprg : ch13.ex1.Free[ch13.ex1.StackOps,Unit] = Suspend(Push(1,Suspend(Pu
//| sh(2,Suspend(Add(Suspend(Pop(Return(())))))))))
def push(value: Int) = Push(value,()) //> push: (value: Int)ch13.ex1.Push[Unit]
def add = Add(()) //> add: => ch13.ex1.Add[Unit]
def sub = Sub(()) //> sub: => ch13.ex1.Sub[Unit]
def mul = Mul(()) //> mul: => ch13.ex1.Mul[Unit]
def end = End(()) //> end: => ch13.ex1.End[Unit]
val stkprg = for {
_ <- push(1)
_ <- push(2)
_ <- add
_ <- push(4)
_ <- mul
} yield () //> stkprg : ch13.ex1.Free[ch13.ex1.StackOps,Unit] = Suspend(Push(1,Suspend(Pu
//| sh(2,Suspend(Add(Suspend(Push(4,Suspend(Mul(Return(())))))))))))
这样从文字意思上描述就清楚多了。但是,这个stkprg到底是干什么的?如果不从文字意义上解释我们根本不知道这段程序干了些什么,怎么干的。换句直白的话就是:没有意义。这正是Free Monad功能精妙之处:我们用Monad for-comprehension来编写一段Monadic program,然后在Interpreter中赋予它具体意义:用Interpreter来确定程序具体的意义。
那我们就进入Interpreter来运算这段程序吧。
先申明Stack类型: type Stack = List[Int]
在上面我们有个Interpreter, foldMap:
def foldMap[G[_]](f: F ~> G)(implicit F: Functor[F], G: Monad[G]): G[A] = resume match {
case Right(a) => G.unit(a)
case Left(k) => G.flatMap(f(k))(_ foldMap f)
}
final def foldRun[B](b: B)(f: (B, F[Free[F,A]]) => (B, Free[F,A]))(implicit F: Functor[F]): (B, A) = {
@annotation.tailrec
def run(t: Free[F,A], z: B): (B, A) = t.resume match {
case Right(a) => (z, a)
case Left(k) => {
val (b1, f1) = f(z, k)
run(f1,b1)
}
}
run(this,b)
}
type Stack = List[Int]
def stackFn(stack: Stack, prg: StackOps[Free[StackOps,Unit]]): (Stack, Free[StackOps,Unit]) = prg match {
case Push(v, n) => {
(v :: stack, n)
}
case Add(n) => {
val hf :: hs :: t = stack
((hf + hs) :: stack, n)
}
case Sub(n) => {
val hf :: hs :: t = stack
((hs - hf) :: stack, n)
}
case Mul(n) => {
val hf :: hs :: t = stack
((hf * hs) :: stack, n)
}
} //> stackFn: (stack: ch13.ex1.Stack, prg: ch13.ex1.StackOps[ch13.ex1.Free[ch13.
//| ex1.StackOps,Unit]])(ch13.ex1.Stack, ch13.ex1.Free[ch13.ex1.StackOps,Unit])
val stkprg = for {
_ <- push(1)
_ <- push(2)
_ <- add
_ <- push(4)
_ <- mul
} yield () //> stkprg : ch13.ex1.Free[ch13.ex1.StackOps,Unit] = Suspend(Push(1,Suspend(Pu
//| sh(2,Suspend(Add(Suspend(Push(4,Suspend(Mul(Return(())))))))))))
stkprg.foldRun(List[Int]())(stackFn) //> res0: (List[Int], Unit) = (List(12, 4, 3, 2, 1),())
我们再试一下用Natural Transformation原理的foldMap函数。我们可以用State的runS来传入Stack初始值:
type StackState[A] = State[Stack,A]
implicit val stackStateMonad = new Monad[StackState] {
def unit[A](a: A) = State(s => (a,s))
def flatMap[A,B](sa: StackState[A])(f: A => StackState[B]): StackState[B] = sa flatMap f
} //> stackStateMonad : ch13.ex1.Monad[ch13.ex1.StackState] = ch13.ex1$$anonfun$
//| main$1$$anon$5@26f67b76
object StackOperator extends (StackOps ~> StackState) {
def apply[A](sa: StackOps[A]): StackState[A] = sa match {
case Push(v,n) => State((s: Stack) => (n, v :: s))
case Add(n) => State((s: Stack) => {
val hf :: hs :: t = s
(n, (hf + hs) :: s)
})
case Sub(n) => State((s: Stack) => {
val hf :: hs :: t = s
(n, (hs - hf) :: s)
})
case Mul(n) => State((s: Stack) => {
val hf :: hs :: t = s
(n, (hf * hs) :: s)
})
}
}
stkprg.foldMap(StackOperator).runS(List[Int]()) //> res1: (Unit, ch13.ex1.Stack) = ((),List(12, 4, 3, 2, 1))
希望通过这些例子能把Free Monad的用途、用法、原理解释清楚了。