很多函数式编程爱好者都把FP称为Monadic Programming,意思是用Monad进行编程。我想FP作为一种比较成熟的编程模式,应该有一套比较规范的操作模式吧。因为Free能把任何F[A]升格成Monad,所以Free的算式(AST)、算法(Interpreter)关注分离(separation of concern)模式应该可以成为一种规范的FP编程模式。我们在前面的几篇讨论中都涉及了一些AST的设计和运算,但都是一些功能单一,离散的例子。如果希望通过Free获取一个完整可用的程序,就必须想办法把离散的Free AST组合成一体运算。我们先从单一的Free AST例子开始:
1 import scalaz._ 2 import Scalaz._ 3 import scala.language.higherKinds 4 import scala.language.implicitConversions 5 object FreeModules { 6 object FreeInteract { 7 trait Interact[+A] 8 type FreeInteract[A] = Free.FreeC[Interact,A] 9 object Interact { 10 case class Ask(prompt: String) extends Interact[String] 11 case class Tell(msg: String) extends Interact[Unit] 12 implicit def interactToFreeC[A](ia: Interact[A]) = Free.liftFC(ia) 13 object InteractConsole extends (Interact ~> Id) { 14 def apply[A](ia: Interact[A]): Id[A] = ia match { 15 case Ask(p) => println(p); readLine 16 case Tell(m) => println(m) 17 } 18 } 19 } 20 import Interact._ 21 val interactScript = for { 22 first <- Ask("What‘s your first name?") 23 last <- Ask("What‘s your last name?") 24 _ <- Tell(s"Hello ${first} ${last}, nice to meet you!") 25 } yield () 26 } 27 }
这是一个我们在前面讨论中重复描述几次的简单交互例子,包括了ADT、AST和Interpreter。我们可以直接运行这个程序:
1 object freePrgDemo extends App { 2 import FreeModules._ 3 import FreeInteract._ 4 import Interact._ 5 Free.runFC(interactScript)(InteractConsole) 6 }
运算结果如下:
1 What‘s your first name? 2 Tiger 3 What‘s your last name? 4 Chan 5 Hello Tiger Chan, nice to meet you!
就是简单的两句界面提示和键盘输入,然后提示输入结果,没什么意义。作为测试,我们也可以模拟Console交互:用Map[String,String]来模拟Map[提问,回答],然后把这个Map提供给Interpreter,返回结果(List[String],A),其中List[String]是运行跟踪记录,A是模拟的键盘输入:
1 type InteractMapTester[A] = Map[String,String] => (List[String], A) 2 implicit val mapTesterMonad = new Monad[InteractMapTester] { 3 def point[A](a: => A) = _ => (List(), a) 4 def bind[A,B](ia: InteractMapTester[A])(f: A => InteractMapTester[B]): InteractMapTester[B] = 5 m => { 6 val (o1,a1) = ia(m) 7 val (o2,a2) = f(a1)(m) 8 (o1 ++ o2, a2) 9 } 10 } 11 object InteractTesterMap extends (Interact ~> InteractMapTester) { 12 def apply[A](ia: Interact[A]): InteractMapTester[A] = ia match { 13 case Ask(p) => { m => (List(), m(p)) } //m(p)返回提问对应的答案作为键盘输入 14 case Tell(s) => { m => (List(s), ()) } //List(s)在bind函数中的o1++o2形成跟踪记录 15 //在运算AST时就会调用InteractMapTester的bind函数 16 } 17 }
使用模拟Console的Interpreter来运行:
1 object freePrgDemo extends App { 2 import FreeModules._ 3 import FreeInteract._ 4 import Interact._ 5 //Free.runFC(interactScript)(InteractConsole) 6 val result = Free.runFC(interactScript)(InteractTesterMap).apply( 7 Map( 8 "What‘s your first name?" -> "tiger", 9 "What‘s your last name?" -> "chan" 10 )) 11 println(result) 12 } 13 //产生以下输出结果 14 (List(Hello tiger chan, nice to meet you!),())
从mapTesterMonad定义中的bind看到了这句:o1++o2,是Logger的典型特征。那么用Writer能不能实现同等效果呢?我们先看看WriterT:
final case class WriterT[F[_], W, A](run: F[(W, A)]) { self => ...
实际上这个W就可以满足Logger的功能,因为在WriterT的flatMap中实现了W|+|W:
def flatMap[B](f: A => WriterT[F, W, B])(implicit F: Bind[F], s: Semigroup[W]): WriterT[F, W, B] = flatMapF(f.andThen(_.run)) def flatMapF[B](f: A => F[(W, B)])(implicit F: Bind[F], s: Semigroup[W]): WriterT[F, W, B] = writerT(F.bind(run){wa => val z = f(wa._2) F.map(z)(wb => (s.append(wa._1, wb._1), wb._2)) })
那么如何把Map[提问,回答]传人呢?我们可以通过WriterT[F[_],W,A]的F[]来实现这一目的:
1 type WriterTF[A] = Map[String,String] => A 2 type InteractWriterTester[A] = WriterT[WriterTF,List[String],A]
然后我们可以用WriterT的参数run来传人Map[String,String]:run:WriterTF[(W,A)] == Map[String,String]=>(W,A)。
以下是用WriterT实现的Interpreter版本:
1 type WriterTF[A] = Map[String,String] => A 2 type InteractWriterTester[A] = WriterT[WriterTF,List[String],A] 3 def testerToWriter[A](f: Map[String,String] => (List[String], A)) = 4 WriterT[WriterTF,List[String],A](f) 5 implicit val writerTesterMonad = WriterT.writerTMonad[WriterTF, List[String]] 6 object InteractTesterWriter extends (Interact ~> InteractWriterTester) { 7 def apply[A](ia: Interact[A]): InteractWriterTester[A] = ia match { 8 case Ask(p) => testerToWriter { m => (List(), m(p)) } 9 case Tell(s) => testerToWriter { m => (List(s), ())} 10 } 11 }
我们可以这样运行:
object freePrgDemo extends App { import FreeModules._ import FreeInteract._ import Interact._ //Free.runFC(interactScript)(InteractConsole) //val result = Free.runFC(interactScript)(InteractTesterMap).apply( val result = Free.runFC(interactScript)(InteractTesterWriter).run( Map( "What‘s your first name?" -> "tiger", "What‘s your last name?" -> "chan" )) println(result) }
我们再设计另一个用户登录Login的例子:
1 object FreeUserLogin { 2 import Dependencies._ 3 trait UserLogin[+A] 4 type FreeUserLogin[A] = Free.FreeC[UserLogin,A] 5 object UserLogin { 6 case class Login(user: String, pswd: String) extends UserLogin[Boolean] 7 implicit def loginToFree[A](ul: UserLogin[A]) = Free.liftFC(ul) 8 type LoginService[A] = Reader[PasswordControl,A] 9 object LoginInterpreter extends (UserLogin ~> LoginService) { 10 def apply[A](ul: UserLogin[A]): LoginService[A] = ul match { 11 case Login(u,p) => Reader( cr => cr.matchPassword(u, p)) 12 } 13 } 14 } 15 import UserLogin._ 16 val loginScript = for { 17 b <- Login("Tiger","1234") 18 } yield b 19 }
这个例子里只有Login一个ADT,它的功能是把输入的User和Password与一个用户登录管理系统内的用户身份信息进行验证。由于如何进行用户密码验证不是这个ADT的功能,它可能涉及另一特殊功能系统的调用,刚好用来做个Reader依赖注入示范。以下是这项依赖定义:
1 object Dependencies { 2 trait PasswordControl { 3 type User = String 4 type Password = String 5 val pswdMap: Map[User, Password] 6 def matchPassword(u: User, p: Password): Boolean 7 } 8 }
对loginScript进行测试运算时必须先获取PasswordControl实例,然后注入运算:
1 import Dependencies._ 2 import FreeUserLogin._ 3 import UserLogin._ 4 object Passwords extends PasswordControl { //依赖实例 5 val pswdMap = Map ( 6 "Tiger" -> "1234", 7 "John" -> "0332" 8 ) 9 def matchPassword(u: User, p: Password) = pswdMap.getOrElse(u, p+"!") === p 10 } 11 val result = Free.runFC(loginScript)(LoginInterpreter).run(Passwords) //注入依赖 12 println(result)
不过即使能够运行,loginScsript的功能明显不完整,还需要像Interact那样的互动部分来获取用户输入信息。那么我们是不是考虑在ADT层次上把Interact和UserLogin合并起来,像这样:
1 case class Ask(prompt: String) extends Interact[String] 2 case class Tell(msg: String) extends Interact[Unit] 3 case class Login(user: String, pswd: String) extends Interact[Boolean]
明显这是可行的。但是,Interact和Login被紧紧捆绑在了一起形成了一个新的ADT。如果我们设计另一个同样需要互动的ADT,我们就需要重复同样的Interact功能设计,显然这样做违背了FP的原则:从功能单一的基本计算开始,按需要对基本函数进行组合实现更复杂的功能。Interact和UserLogin都是基础ADT,从编程语言角度描述Interact和UserLogin属于两种类型的编程语句。我们最终需要的AST是这样的:
1 val interLogin: Free[???, A] = for { 2 user <- Ask("Enter User ID:") //Free[Interact,A] 3 pswd <- Ask("Enter Password:") //Free[Interact,A] 4 ok <- Login(user,pswd) //Free[UserLogin,A] 5 } yield ok
不过明显类型对不上,因为Interact和UserLogin是两种语句。scalaz的Coproduct类型可以帮助我们实现两种Monadic语句的语义(sematics)合并。Coproduct是这样定义的:scalaz/Coproduct.scala
/** `F` on the left, and `G` on the right, of [[scalaz.\/]]. * * @param run The underlying [[scalaz.\/]]. */ final case class Coproduct[F[_], G[_], A](run: F[A] \/ G[A]) { import Coproduct._ def map[B](f: A => B)(implicit F: Functor[F], G: Functor[G]): Coproduct[F, G, B] = Coproduct(run.bimap(F.map(_)(f), G.map(_)(f))) ...
从run:F[A]\/G[A]可以理解Coproduct是两种语句F,G的联合(union)。在我们上面的例子里我们可以用下面的表达方式代表Interact和UserLogin两种语句的联合(union):
1 type InteractLogin[A] = Coproduct[Interact,UserLogin,A]
这是一个语义更广泛的类型:包含了Interact和UserLogin语义。我们可以用Inject类型来把Interact和UserLogin语句集“注入”到一个更大的句集。Inject是这样定义的:scalaz/Inject.scala
/** * Inject type class as described in "Data types a la carte" (Swierstra 2008). * * @see [[http://www.staff.science.uu.nl/~swier004/Publications/DataTypesALaCarte.pdf]] */ sealed abstract class Inject[F[_], G[_]] { def inj[A](fa: F[A]): G[A] def prj[A](ga: G[A]): Option[F[A]] } sealed abstract class InjectInstances { implicit def reflexiveInjectInstance[F[_]] = new Inject[F, F] { def inj[A](fa: F[A]) = fa def prj[A](ga: F[A]) = some(ga) } implicit def leftInjectInstance[F[_], G[_]] = new Inject[F, ({type λ[α] = Coproduct[F, G, α]})#λ] { def inj[A](fa: F[A]) = Coproduct.leftc(fa) def prj[A](ga: Coproduct[F, G, A]) = ga.run.fold(some(_), _ => none) } implicit def rightInjectInstance[F[_], G[_], H[_]](implicit I: Inject[F, G]) = new Inject[F, ({type λ[α] = Coproduct[H, G, α]})#λ] { def inj[A](fa: F[A]) = Coproduct.rightc(I.inj(fa)) def prj[A](ga: Coproduct[H, G, A]) = ga.run.fold(_ => none, I.prj(_)) } } ...
实现函数inj(fa:F[A]):G[A]代表把F[A]并入G[A]。这里还提供了三个类型的实例:
1、reflexiceInjectInstance[F[_]]:自我注入
2、leftInjectInstance[F[_],G[_]]:把F[A]注入Coproduct[F,G,A]的left(-\/)
3、rightInjectInstance[F[_],G[_],H[_]]:把F[A]注入Coproduct的right(\/-)。需要先把F注入G(inj(F[A]):G[A])
我们可以用implicitly来证明Interact和UserLogin的Inject实例存在:
1 val selfInj = implicitly[Inject[Interact,Interact]] 2 type LeftInterLogin[A] = Coproduct[Interact,UserLogin,A] 3 val leftInj = implicitly[Inject[Interact,LeftInterLogin]] 4 type RightInterLogin[A] = Coproduct[UserLogin,LeftInterLogin,A] 5 val rightInj = implicitly[Inject[Interact,RightInterLogin]]
现在我们需要把Coproduct[F,G,A]的F与G合并然后把F[A]升格成Free[G,A]:
1 object coproduct { 2 def lift[F[_],G[_],A](fa: F[A])(implicit I: Inject[F,G]): Free.FreeC[G,A] = Free.liftFC(I.inj(fa)) 3 }
我们可以用这个lift把Interact和UserLogin的ADT统一升格成Free[G,A]:
1 object coproduct { 2 import FreeInteract._ 3 import Interact._ 4 import FreeUserLogin._ 5 import UserLogin._ 6 def lift[F[_],G[_],A](fa: F[A])(implicit I: Inject[F,G]): Free.FreeC[G,A] = Free.liftFC(I.inj(fa)) 7 class Interacts[G[_]](implicit I: Inject[Interact,G]) { 8 def ask(prompt: String): Free.FreeC[G,String] = lift(Ask(prompt)) 9 def tell(msg: String): Free.FreeC[G,Unit] = lift(Tell(msg)) 10 } 11 class Logins[G[_]](implicit I: Inject[UserLogin,G]) { 12 def login(u: String, p: String): Free.FreeC[G,Boolean] = lift(Login(u,p)) 13 } 14 }
我们用lift把基础Interact和UserLogin的语句注入了联合的语句集G[A],然后升格成FreeC[G,A]。现在我们可以把Interact,UserLogin这两种语句用在同一个for-comprehension里了:
1 def loginScript[G[_]](implicit I: Interacts[G], L: Logins[G]) ={ 2 import I._ 3 import L._ 4 for { 5 uid <- ask("ya id?") 6 pwd <- ask("password?") 7 login <- login(uid,pwd) 8 _ <- if (login) tell("ya lucky bastard!") else tell("geda fk outa here!") 9 } yield() 10 }
有了Inject和Lift,现在已经成功的用两种ADT集成了一个AST。不过我们还必须提供Interacts[G]和Logins[G]实例:
1 object CoproductModules { 2 object CoproductFunctions { 3 import FreeInteract._ 4 import Interact._ 5 import FreeUserLogin._ 6 import UserLogin._ 7 def lift[F[_],G[_],A](fa: F[A])(implicit I: Inject[F,G]): Free.FreeC[G,A] = Free.liftFC(I.inj(fa)) 8 class Interacts[G[_]](implicit I: Inject[Interact,G]) { 9 def ask(prompt: String): Free.FreeC[G,String] = lift(Ask(prompt)) 10 def tell(msg: String): Free.FreeC[G,Unit] = lift(Tell(msg)) 11 } 12 object Interacts { 13 implicit def instance[G[_]](implicit I: Inject[Interact,G]) = new Interacts[G] 14 } 15 class Logins[G[_]](implicit I: Inject[UserLogin,G]) { 16 def login(u: String, p: String): Free.FreeC[G,Boolean] = lift(Login(u,p)) 17 } 18 object Logins { 19 implicit def instance[G[_]](implicit I: Inject[UserLogin,G]) = new Logins[G] 20 } 21 }
现在我们的语句集(AST)是一个联合的语句集(Coproduct)。那么,我们应该怎么去运算?它呢?我们应该如何实现它的Interpreter?现在我们面对的Monadic程序类型是个Coproduct:
1 type InteractLogin[A] = Coproduct[Interact,UserLogin,A] 2 val loginPrg = loginScript[InteractLogin]
现在语句集Interact和UserLogin是分别放在Coproduce的左右两边。那么我们可以历遍这个Coproduct来分别运算Interact和UserLogin语句:
1 def or[F[_],G[_],H[_]](fg: F ~> G, hg: H ~> G): ({type l[x] = Coproduct[F,H,x]})#l ~> G = 2 new (({type l[x] = Coproduct[F,H,x]})#l ~> G) { 3 def apply[A](ca: Coproduct[F,H,A]): G[A] = ca.run match { 4 case -\/(fa) => fg(fa) 5 case \/-(ha) => hg(ha) 6 } 7 }
值得注意的是如果or函数用在Interact和UserLogin上时它们自然转换(NaturalTransformation)的目标类型必须一致,应该是一个更大的类型,而且必须是Monad,这是NaturalTransformation的要求。所以我们可以把InteractInterpreter的转换目标类型由Id变成Reader,也就是LoginInterpreter的转换目标类型:
1 object InteractReader extends (Interact ~> LoginService) { 2 def apply[A](ia: Interact[A]): LoginService[A] = ia match { 3 case Ask(p) => println(p); Reader(cr => readLine) 4 case Tell(m) => println(m); Reader(cr => ()) 5 } 6 }
好了,现在我们可以这样来测试运算:
1 object freePrgDemo extends App { 2 import FreeModules._ 3 import FreeInteract._ 4 import Interact._ 5 //Free.runFC(interactScript)(InteractConsole) 6 //val result = Free.runFC(interactScript)(InteractTesterMap).apply( 7 /* val result = Free.runFC(interactScript)(InteractTesterWriter).run( 8 Map( 9 "What‘s your first name?" -> "tiger", 10 "What‘s your last name?" -> "chan" 11 )) 12 println(result) 13 */ 14 import Dependencies._ 15 import FreeUserLogin._ 16 import UserLogin._ 17 18 object Passwords extends PasswordControl { 19 val pswdMap = Map ( 20 "Tiger" -> "1234", 21 "John" -> "0332" 22 ) 23 def matchPassword(u: User, p: Password) = pswdMap.getOrElse(u, p+"!") === p 24 } 25 /* 26 val result = Free.runFC(loginScript)(LoginInterpreter).run(Passwords) 27 println(result) 28 */ 29 30 import CoproductDemo._ 31 Free.runFC(loginPrg)(or(InteractReader,LoginInterpreter)).run(Passwords) 32 }
我们把密码管理依赖也注入进去了。看看结果:
1 ya id? 2 Tiger 3 password? 4 2012 5 geda fk outa here! 6 7 ya id? 8 Tiger 9 password? 10 1234 11 ya lucky bastard! 12 13 ya id? 14 John 15 password? 16 0332 17 ya lucky bastard!
OK, 把这节示范源代码提供在下面:
1 package demos 2 import scalaz._ 3 import Scalaz._ 4 import scala.language.higherKinds 5 import scala.language.implicitConversions 6 object FreeModules { 7 object FreeInteract { 8 trait Interact[+A] 9 type FreeInteract[A] = Free.FreeC[Interact,A] 10 object Interact { 11 case class Ask(prompt: String) extends Interact[String] 12 case class Tell(msg: String) extends Interact[Unit] 13 implicit def interactToFreeC[A](ia: Interact[A]) = Free.liftFC(ia) 14 object InteractConsole extends (Interact ~> Id) { 15 def apply[A](ia: Interact[A]): Id[A] = ia match { 16 case Ask(p) => println(p); readLine 17 case Tell(m) => println(m) 18 } 19 } 20 type InteractMapTester[A] = Map[String,String] => (List[String], A) 21 implicit val mapTesterMonad = new Monad[InteractMapTester] { 22 def point[A](a: => A) = _ => (List(), a) 23 def bind[A,B](ia: InteractMapTester[A])(f: A => InteractMapTester[B]): InteractMapTester[B] = 24 m => { 25 val (o1,a1) = ia(m) 26 val (o2,a2) = f(a1)(m) 27 (o1 ++ o2, a2) 28 } 29 } 30 object InteractTesterMap extends (Interact ~> InteractMapTester) { 31 def apply[A](ia: Interact[A]): InteractMapTester[A] = ia match { 32 case Ask(p) => { m => (List(), m(p)) } //m(p)返回提问对应的答案作为键盘输入 33 case Tell(s) => { m => (List(s), ()) } //List(s)在bind函数中的o1++o2形成跟踪记录 34 //在运算AST时会用到InteractMapTester的bind 35 } 36 } 37 type WriterTF[A] = Map[String,String] => A 38 type InteractWriterTester[A] = WriterT[WriterTF,List[String],A] 39 def testerToWriter[A](f: Map[String,String] => (List[String], A)) = 40 WriterT[WriterTF,List[String],A](f) 41 implicit val writerTesterMonad = WriterT.writerTMonad[WriterTF, List[String]] 42 object InteractTesterWriter extends (Interact ~> InteractWriterTester) { 43 def apply[A](ia: Interact[A]): InteractWriterTester[A] = ia match { 44 case Ask(p) => testerToWriter { m => (List(), m(p)) } 45 case Tell(s) => testerToWriter { m => (List(s), ())} 46 } 47 } 48 } 49 import Interact._ 50 val interactScript = for { 51 first <- Ask("What‘s your first name?") 52 last <- Ask("What‘s your last name?") 53 _ <- Tell(s"Hello ${first} ${last}, nice to meet you!") 54 } yield () 55 } 56 object FreeUserLogin { 57 import Dependencies._ 58 trait UserLogin[+A] 59 type FreeUserLogin[A] = Free.FreeC[UserLogin,A] 60 object UserLogin { 61 case class Login(user: String, pswd: String) extends UserLogin[Boolean] 62 implicit def loginToFree[A](ul: UserLogin[A]) = Free.liftFC(ul) 63 type LoginService[A] = Reader[PasswordControl,A] 64 object LoginInterpreter extends (UserLogin ~> LoginService) { 65 def apply[A](ul: UserLogin[A]): LoginService[A] = ul match { 66 case Login(u,p) => Reader( cr => cr.matchPassword(u, p)) 67 } 68 } 69 } 70 import UserLogin._ 71 val loginScript = for { 72 b <- Login("Tiger","1234") 73 } yield b 74 } 75 } 76 object Dependencies { 77 trait PasswordControl { 78 type User = String 79 type Password = String 80 val pswdMap: Map[User, Password] 81 def matchPassword(u: User, p: Password): Boolean 82 } 83 } 84 object CoproductDemo { 85 import FreeModules._ 86 import FreeUserLogin._ 87 import UserLogin._ 88 import FreeInteract._ 89 import Interact._ 90 import Dependencies._ 91 def lift[F[_],G[_],A](fa: F[A])(implicit I: Inject[F,G]): Free.FreeC[G,A] = Free.liftFC(I.inj(fa)) 92 class Interacts[G[_]](implicit I: Inject[Interact,G]) { 93 def ask(prompt: String) = lift(Ask(prompt)) 94 def tell(msg: String) = lift(Tell(msg)) 95 } 96 object Interacts { 97 implicit def instance[F[_]](implicit I: Inject[Interact,F]) = new Interacts[F] 98 } 99 class Logins[G[_]](implicit I: Inject[UserLogin,G]) { 100 def login(user: String, pswd: String) = lift(Login(user,pswd)) 101 } 102 object Logins { 103 implicit def instance[F[_]](implicit I: Inject[UserLogin,F]) = new Logins[F] 104 } 105 def loginScript[G[_]](implicit I: Interacts[G], L: Logins[G]) ={ 106 import I._ 107 import L._ 108 for { 109 uid <- ask("ya id?") 110 pwd <- ask("password?") 111 login <- login(uid,pwd) 112 _ <- if (login) tell("ya lucky bastard!") else tell("geda fk outa here!") 113 } yield() 114 } 115 116 def or[F[_],G[_],H[_]](fg: F ~> G, hg: H ~> G): ({type l[x] = Coproduct[F,H,x]})#l ~> G = 117 new (({type l[x] = Coproduct[F,H,x]})#l ~> G) { 118 def apply[A](ca: Coproduct[F,H,A]): G[A] = ca.run match { 119 case -\/(fa) => fg(fa) 120 case \/-(ha) => hg(ha) 121 } 122 } 123 124 type InteractLogin[A] = Coproduct[Interact,UserLogin,A] 125 val loginPrg = loginScript[InteractLogin] 126 object InteractReader extends (Interact ~> LoginService) { 127 def apply[A](ia: Interact[A]): LoginService[A] = ia match { 128 case Ask(p) => println(p); Reader(cr => readLine) 129 case Tell(m) => println(m); Reader(cr => ()) 130 } 131 } 132 133 } 134 135 object freePrgDemo extends App { 136 import FreeModules._ 137 import FreeInteract._ 138 import Interact._ 139 //Free.runFC(interactScript)(InteractConsole) 140 //val result = Free.runFC(interactScript)(InteractTesterMap).apply( 141 /* val result = Free.runFC(interactScript)(InteractTesterWriter).run( 142 Map( 143 "What‘s your first name?" -> "tiger", 144 "What‘s your last name?" -> "chan" 145 )) 146 println(result) 147 */ 148 import Dependencies._ 149 import FreeUserLogin._ 150 import UserLogin._ 151 152 object Passwords extends PasswordControl { 153 val pswdMap = Map ( 154 "Tiger" -> "1234", 155 "John" -> "0332" 156 ) 157 def matchPassword(u: User, p: Password) = pswdMap.getOrElse(u, p+"!") === p 158 } 159 /* 160 val result = Free.runFC(loginScript)(LoginInterpreter).run(Passwords) 161 println(result) 162 */ 163 164 import CoproductDemo._ 165 Free.runFC(loginPrg)(or(InteractReader,LoginInterpreter)).run(Passwords) 166 }