Scalaz(39)- Free :a real monadic program

来源:互联网 发布:明阳智慧能源知乎 编辑:程序博客网 时间:2024/05/21 10:19

   一直感觉FP比较虚,可能太多学术性的东西,不知道如何把这些由数学理论在背后支持的一套全新数据类型和数据结构在现实开发中加以使用。直到Free Monad,才真正感觉能用FP方式进行编程了。在前面我们已经花了不小篇幅来了解Free Monad,这次我想跟大家讨论一下用Free Monad来编写一个真正能运行的完整应用程序。当然,这个程序必须具备FP特性,比如函数组合(function composition),纯代码(pure code),延迟副作用(delayed side effect)等等。我们这次模拟的一个应用场景是这样的:模拟一个计算器程序,用户先用密码登录;然后选择操作,包括加、减、乘、除;系统验证用户的操作权限;输入第一个数字,输入另一个数字,系统给出计算结果。程序在用户通过了密码登录后循环运行。我们先把程序要求里的一些操作语句集罗列出来:

1、人机交互,Interact

2、用户登录,Login

3、权限控制,Permission

4、算术运算,Calculator

这其中Login,Permission,Calculator都必须与Interact组合使用,因为它们都需要交互式人工输入。这次我们把讨论流程反过来:先把这个程序完整的算式(Algebraic Data Tree)、算法(Interpreter)以及依赖注入、运算、结果等等先摆出来,然后再逐段分析说明:

package run.demoimport scalaz._import Scalaz._import scala.language.higherKindsimport scala.language.implicitConversionsimport run.demo.Modules.FreeCalculator.CalcInterpobject Modules {  object FreeInteract {    trait Interact[+NextAct]    object Interact {      case class Ask[NextAct](prompt: String, onInput: String => NextAct) extends Interact[NextAct]      case class Tell[NextAct](msg: String, n: NextAct) extends Interact[NextAct]      implicit object interactFunctor extends Functor[Interact] {         def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ia match {           case Ask(p,onInput) => Ask(p, onInput andThen f)           case Tell(m,n) => Tell(m, f(n))         }      }     }    import Interact._    object InteractConsole extends (Interact ~> Id) {      def apply[A](ia: Interact[A]): Id[A] = ia match {        case Ask(p,onInput) => println(p); onInput(readLine)        case Tell(m, n) => println(m); n      }    }    import FreeLogin._    object InteractLogin extends (Interact ~> PasswordReader) {      def apply[A](ia: Interact[A]): PasswordReader[A] = ia match {        case Ask(p,onInput) => println(p); Reader(m => onInput(readLine))        case Tell(m, n) => println(m); Reader(m => n)      }    }    import FreePermission._    object InteractPermission extends(Interact ~> PermissionReader) {      def apply[A](ia: Interact[A]): PermissionReader[A] = ia match {        case Ask(p,onInput) => println(p);Reader(m => onInput(readLine))        case Tell(m,n) => println(m); Reader(m => n)      }    }  }  object FreeLogin {    trait UserLogin[+A]    object UserLogin {      case class Login(uid: String, pswd: String) extends UserLogin[Boolean]    }     import UserLogin._    import Dependencies._    type PasswordReader[A] = Reader[PasswordControl, A]    object LoginInterp extends (UserLogin ~> PasswordReader) {      def apply[A](la: UserLogin[A]): PasswordReader[A] = la match {        case Login(uid,pswd) => Reader(m => m.matchPassword(uid, pswd))      }    }  }  object FreePermission {    trait Permission[+A]    object Permission {      case class HasPermission(uid: String, opr: String) extends Permission[Boolean]    }    import Dependencies._    import Permission._    type PermissionReader[A] = Reader[PermissionControl,A]    object PermissionInterp extends (Permission ~> PermissionReader) {      def apply[A](pa: Permission[A]): PermissionReader[A] = pa match {        case HasPermission(uid,opr) => Reader {m => m.matchPermission(uid, opr)}      }    }  }  object FreeCalculator {    trait Calculator[+A]    object Calculator {      case class Calc(opr: String, lop: Int, rop: Int) extends Calculator[Int]    }    import Calculator._    object CalcInterp extends (Calculator ~> Id) {      def apply[A](ca: Calculator[A]): Id[A] = ca match {        case Calc(opr,op1,op2) => opr.toUpperCase match {          case "ADD" => op1 + op2          case "SUB" => op1 - op2          case "MUL" => op1 * op2          case "DIV" => op1 / op2        }      }    }  }  object FreeFunctions {    import FreeInteract._    import Interact._    import FreeLogin._    import UserLogin._    import FreePermission._    import Permission._    import FreeCalculator._    import Calculator._    def lift[F[_],G[_],A](fa: F[A])(implicit I: Inject[F,G]): Free.FreeC[G,A] =        Free.liftFC(I.inj(fa))     class Interacts[G[_]](implicit I: Inject[Interact,G]) {      def ask[A](prompt: String, onInput: String => A) = Free.liftFC(I.inj(Ask(prompt, onInput)))      def tell[A](msg: String) = Free.liftFC(I.inj(Tell(msg, ())))    }    object Interacts {      implicit def instance[F[_]](implicit I: Inject[Interact,F]) = new Interacts[F]    }    class Logins[G[_]](implicit I: Inject[UserLogin,G]) {      def login(uid: String, pswd: String) = lift(Login(uid,pswd))    }    object Logins {      implicit def instance[F[_]](implicit I: Inject[UserLogin,F]) = new Logins[F]    }    class Permissions[G[_]](implicit I: Inject[Permission,G]) {      def hasPermission(uid: String, opr: String) = lift(HasPermission(uid,opr))    }    object Permissions {      implicit def instance[F[_]](implicit I: Inject[Permission,F]) = new Permissions[F]    }    class Calculators[G[_]](implicit I: Inject[Calculator,G]) {      def calc(opr: String, op1: Int, op2: Int) = lift(Calc(opr,op1,op2))    }    object Calculators {      implicit def instance[F[_]](implicit I: Inject[Calculator,F]) = new Calculators[F]    }    def or[F[_],H[_],G[_]](fg: F ~> G, hg: H ~> G): ({type l[x] = Coproduct[F,H,x]})#l ~> G =      new (({type l[x] = Coproduct[F,H,x]})#l ~> G) {       def apply[A](ca: Coproduct[F,H,A]): G[A] = ca.run match {         case -\/(x) => fg(x)         case \/-(y) => hg(y)       }    }  }  object FreeProgs {    import FreeFunctions._    import FreeInteract._    import FreeLogin._    import FreePermission._    import FreeCalculator._    def freeCMonad[S[_]] = Free.freeMonad[({type l[x] = Coyoneda[S,x]})#l]    def loginScript[F[_]](implicit I: Interacts[F], L: Logins[F]) = {      import I._      import L._      for {        uid <- ask("ya id:",identity)        pwd <- ask("password:",identity)        login <- login(uid,pwd)        _ <- if (login) tell("ya in, ya lucky bastard!")                else tell("geta fk outa here!")        usr <- if (login) freeCMonad[F].point(uid)                else freeCMonad[F].point("???")      } yield usr    }    def accessScript[F[_]](uid: String)(implicit I: Interacts[F], P: Permissions[F]) = {      import I._      import P._      for {        inp <- ask("votiu vangto do?",identity)        cando <- hasPermission(uid,inp)        _ <- if (cando) tell("ok, go on ...")                else tell("na na na, cant do that!")           opr <- if (cando) freeCMonad[F].point(inp)                else freeCMonad[F].point("XXX")      } yield opr           }    def calcScript[F[_]](opr: String)(implicit I: Interacts[F], C: Calculators[F]) = {      import I._;import C._;      for {        op1 <- ask("fus num:", _.toInt)        op2 <- ask("nx num:", _.toInt)        result <- calc(opr,op1,op2)      } yield result    }    type LoginScript[A] = Coproduct[Interact, UserLogin, A]    type CalcScript[A] = Coproduct[Interact, Calculator, A]    type AccessScript[A] = Coproduct[Interact, Permission, A]    val accessPrg = accessScript[AccessScript] _    val loginPrg = loginScript[LoginScript]    val calcPrg = calcScript[CalcScript] _  }}object Dependencies {  trait PasswordControl {    val pswdMap: Map[String,String]    def matchPassword(uid: String, pswd: String): Boolean  }  trait PermissionControl {    val permMap: Map[String,List[String]]    def matchPermission(uid: String, operation: String): Boolean  }}object FreeProgram extends App {  import Modules._  import FreeInteract._  import FreeLogin._  import FreePermission._  import FreeFunctions._  import FreeProgs._  import Dependencies._  object Passwords extends PasswordControl {     val pswdMap = Map (       "Tiger" -> "1234",       "John" -> "0332"     )     def matchPassword(uid: String, pswd: String) = pswdMap.getOrElse(uid, pswd+"!") === pswd  }  object AccessRights extends PermissionControl {     val permMap = Map (       "Tiger" -> List("Add","Sub"),       "John" -> List("Mul","Div")     )     def matchPermission(uid: String, opr: String) = permMap.getOrElse(uid, List()).exists { _ === opr}  }    val uid = Free.runFC(loginPrg)(or(InteractLogin, LoginInterp)).run(Passwords)  val opr = Free.runFC(accessScript[AccessScript](uid))(or(InteractPermission, PermissionInterp)).run(AccessRights)  val sum = Free.runFC(calcScript[CalcScript](opr))(or(InteractConsole, CalcInterp))  println(uid)  println(opr)  println(sum)}//测试运算结果ya id:Tigerpassword:1234ya in, ya lucky bastard!votiu vangto do?Addok, go on ...fus num:3nx num:7TigerAdd10


看起来好像费了老大劲就做那么点事。但如果我们按照Free Monadic编程的规范来做,一切仅仅有条无需多想,那也就是那么点事。实际上在编写更大型更复杂的程序时应该会觉着思路更清晰,代码量会更精简,因为成功的函数组合可以避免许多重复代码。基本的Free Monadic 编程步骤大体如下:

1、ADT design  

2、ADT Free lifting

3、ADT composition、AST composition

4、Dependency design

5、Interpreter design

6、Running and dependency injection

1、ADTs: 按照功能要求设计编程语句。其中值得注意的是Interact:

   trait Interact[+NextAct]    object Interact {      case class Ask[NextAct](prompt: String, onInput: String => NextAct) extends Interact[NextAct]      case class Tell[NextAct](msg: String, n: NextAct) extends Interact[NextAct]      implicit object interactFunctor extends Functor[Interact] {         def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ia match {           case Ask(p,onInput) => Ask(p, onInput andThen f)           case Tell(m,n) => Tell(m, f(n))         }      }     } 

Interact能够支持map,必须是个Functor。这是因为其中一个状态Ask需要对输入String进行转换后进入下一个状态。

2、升格lifting:我们需要把这些ADT都升格成Free。因为有些ADT不是Functor,所以用liftFC把它们统一升格为FreeC:

  object FreeFunctions {    import FreeInteract._    import Interact._    import FreeLogin._    import UserLogin._    import FreePermission._    import Permission._    import FreeCalculator._    import Calculator._    def lift[F[_],G[_],A](fa: F[A])(implicit I: Inject[F,G]): Free.FreeC[G,A] =        Free.liftFC(I.inj(fa))     class Interacts[G[_]](implicit I: Inject[Interact,G]) {      def ask[A](prompt: String, onInput: String => A) = Free.liftFC(I.inj(Ask(prompt, onInput)))      def tell[A](msg: String) = Free.liftFC(I.inj(Tell(msg, ())))    }    object Interacts {      implicit def instance[F[_]](implicit I: Inject[Interact,F]) = new Interacts[F]    }    class Logins[G[_]](implicit I: Inject[UserLogin,G]) {      def login(uid: String, pswd: String) = lift(Login(uid,pswd))    }    object Logins {      implicit def instance[F[_]](implicit I: Inject[UserLogin,F]) = new Logins[F]    }    class Permissions[G[_]](implicit I: Inject[Permission,G]) {      def hasPermission(uid: String, opr: String) = lift(HasPermission(uid,opr))    }    object Permissions {      implicit def instance[F[_]](implicit I: Inject[Permission,F]) = new Permissions[F]    }    class Calculators[G[_]](implicit I: Inject[Calculator,G]) {      def calc(opr: String, op1: Int, op2: Int) = lift(Calc(opr,op1,op2))    }    object Calculators {      implicit def instance[F[_]](implicit I: Inject[Calculator,F]) = new Calculators[F]    }    def or[F[_],H[_],G[_]](fg: F ~> G, hg: H ~> G): ({type l[x] = Coproduct[F,H,x]})#l ~> G =      new (({type l[x] = Coproduct[F,H,x]})#l ~> G) {       def apply[A](ca: Coproduct[F,H,A]): G[A] = ca.run match {         case -\/(x) => fg(x)         case \/-(y) => hg(y)       }    }  }


在lift函数中使用了scalaz提供的Inject类型实例,用来把F[A]这种类型转换成G[A]。可以理解为把一组语句F[A]注入更大的语句集G[A](G[A]可以是F[A],这时转换结果为一摸一样的语句集)。可能因为Interact和其它ADT不同,是个Functor,所以在调用lift函数进行升格时compiler会产生错误类型推导结果,直接调用liftFC可以解决问题,这个留到以后继续研究。现在这些升格了的语句集都具备了隐式实例implicit instance,随时可以在隐式解析域内提供操作语句支持。

3、ASTs:现在有了这些基础语句集,按照功能要求,我们可以用某一种语句组合成一个程序AST,或者结合用两种以上语句组合程序,甚至把产生的AST组合成更大的程序。我们可以用scalaz的Coproduct来实现这些语句集的联合:

    type LoginScript[A] = Coproduct[Interact, UserLogin, A]    type CalcScript[A] = Coproduct[Interact, Calculator, A]    type AccessScript[A] = Coproduct[Interact, Permission, A]    val accessPrg = accessScript[AccessScript] _    val loginPrg = loginScript[LoginScript]    val calcPrg = calcScript[CalcScript] _


这里有个环节特别需要注意:理论上我们可以用Coproduct联合两种以上语句集:

    type F0[A] = Coproduct[Interact,UserLogin,A]    type F1[A] = Coproduct[Permission,F0,A]    type F2[A] = Coproduct[Calculator,F1,A]    val loginPrg2 = loginScript[F1]

但loginPrg2编译错误:

not enough arguments for method loginScript: (implicit I: run.demo.Modules.FreeFunctions.Interacts[run.demo.Modules.FreeProgs.F1], implicit L: run.demo.Modules.FreeFunctions.Logins[run.demo.Modules.FreeProgs.F1], implicit P: run.demo.Modules.FreeFunctions.Permissions[run.demo.Modules.FreeProgs.F1])scalaz.Free[[x]scalaz.Coyoneda[run.demo.Modules.FreeProgs.F1,x],String]. Unspecified value parameters L, P.


我初步分析可能是因为scalaz对Free设下的门槛:F[A]必须是个Functor。在lift函数的Inject[F,G]中,目标类型G[_]最终会被升格为Free Monad,如果我们使用Free.liftF函数的话G[_]必须是Functor。可能使用Free.liftFC后造成compiler无法正常进行类型推断吧。最近新推出的Cats组件库中Free的定义不需要Functor,有可能解决这个问题。因为Free可能成为将来的一种主要编程模式,所以必须想办法解决多语句集联合使用的问题。不过我们把这个放到以后再说。

现在我们可以用升格了的语句编程了,也就是函数组合:

 object FreeProgs {    import FreeFunctions._    import FreeInteract._    import FreeLogin._    import FreePermission._    import FreeCalculator._    def freeCMonad[S[_]] = Free.freeMonad[({type l[x] = Coyoneda[S,x]})#l]    def loginScript[F[_]](implicit I: Interacts[F], L: Logins[F]) = {      import I._      import L._      for {        uid <- ask("ya id:",identity)        pwd <- ask("password:",identity)        login <- login(uid,pwd)        _ <- if (login) tell("ya in, ya lucky bastard!")                else tell("geta fk outa here!")        usr <- if (login) freeCMonad[F].point(uid)                else freeCMonad[F].point("???")      } yield uid    }    def accessScript[F[_]](uid: String)(implicit I: Interacts[F], P: Permissions[F]) = {      import I._      import P._      for {        inp <- ask("votiu vangto do?",identity)        cando <- hasPermission(uid,inp)        _ <- if (cando) tell("ok, go on ...")                else tell("na na na, cant do that!")           opr <- if (cando) freeCMonad[F].point(inp)                else freeCMonad[F].point("XXX")      } yield inp           }    def calcScript[F[_]](opr: String)(implicit I: Interacts[F], C: Calculators[F]) = {      import I._;import C._;      for {        op1 <- ask("fus num:", _.toInt)        op2 <- ask("nx num:", _.toInt)        result <- calc(opr,op1,op2)      } yield result    }    type LoginScript[A] = Coproduct[Interact, UserLogin, A]    type CalcScript[A] = Coproduct[Interact, Calculator, A]    type AccessScript[A] = Coproduct[Interact, Permission, A]    val accessPrg = accessScript[AccessScript] _    val loginPrg = loginScript[LoginScript]    val calcPrg = calcScript[CalcScript] _     }

可以看出,以上每一个程序都比较简单,容易理解。这也是FP的特点:从简单基本的程序开始,经过不断组合形成完整应用。

4、Dependency injection:稍有规模的程序都有可能需要依赖其它程序来提供一些功能。所以在这个例子里示范了一些依赖注入:

object Dependencies {  trait PasswordControl {    val pswdMap: Map[String,String]    def matchPassword(uid: String, pswd: String): Boolean  }  trait PermissionControl {    val permMap: Map[String,List[String]]    def matchPermission(uid: String, operation: String): Boolean  }}


5、Interpreter:在运算程序时(program interpretation),可以根据需要调用依赖中的功能:

    import Dependencies._    type PasswordReader[A] = Reader[PasswordControl, A]    object LoginInterp extends (UserLogin ~> PasswordReader) {      def apply[A](la: UserLogin[A]): PasswordReader[A] = la match {        case Login(uid,pswd) => Reader(m => m.matchPassword(uid, pswd))      }    }


注意,当两种语句联合使用时,它们会被转换(natural transformation)成同一个目标语句集,所以当Interact和UserLogin联合使用时都会进行PasswordReader类型的转换。由于Interact是一项最基本的功能,与其它ADT联合使用发挥功能,所以要为每个联合ADT提供特殊的Interpreter:

    object InteractConsole extends (Interact ~> Id) {      def apply[A](ia: Interact[A]): Id[A] = ia match {        case Ask(p,onInput) => println(p); onInput(readLine)        case Tell(m, n) => println(m); n      }    }    import FreeLogin._    object InteractLogin extends (Interact ~> PasswordReader) {      def apply[A](ia: Interact[A]): PasswordReader[A] = ia match {        case Ask(p,onInput) => println(p); Reader(m => onInput(readLine))        case Tell(m, n) => println(m); Reader(m => n)      }    }    import FreePermission._    object InteractPermission extends(Interact ~> PermissionReader) {      def apply[A](ia: Interact[A]): PermissionReader[A] = ia match {        case Ask(p,onInput) => println(p);Reader(m => onInput(readLine))        case Tell(m,n) => println(m); Reader(m => n)      }    }


同样,联合语句集编成的程序必须有相应的运算方法。我们特别为Coproduct类型的运算提供了or函数:

    def or[F[_],H[_],G[_]](fg: F ~> G, hg: H ~> G): ({type l[x] = Coproduct[F,H,x]})#l ~> G =      new (({type l[x] = Coproduct[F,H,x]})#l ~> G) {       def apply[A](ca: Coproduct[F,H,A]): G[A] = ca.run match {         case -\/(x) => fg(x)         case \/-(y) => hg(y)       }


Coproduce是把两个语句集放在左右两边。我们只需要历遍Coproduct结构逐个运算结构中的语句。

6、running program:由于我们把所有语句都升格成了FreeC类型,所以必须调用runFC函数来运行。作为FP程序延迟副作用示范,我们在程序真正运算时才把依赖注入进去:

object FreeProgram extends App {  import Modules._  import FreeInteract._  import FreeLogin._  import FreePermission._  import FreeFunctions._  import FreeProgs._  import Dependencies._  object Passwords extends PasswordControl {     val pswdMap = Map (       "Tiger" -> "1234",       "John" -> "0332"     )     def matchPassword(uid: String, pswd: String) = pswdMap.getOrElse(uid, pswd+"!") === pswd  }  object AccessRights extends PermissionControl {     val permMap = Map (       "Tiger" -> List("Add","Sub"),       "John" -> List("Mul","Div")     )     def matchPermission(uid: String, opr: String) = permMap.getOrElse(uid, List()).exists { _ === opr}  }    val uid = Free.runFC(loginPrg)(or(InteractLogin, LoginInterp)).run(Passwords)  val opr = Free.runFC(accessScript[AccessScript](uid))(or(InteractPermission, PermissionInterp)).run(AccessRights)  val sum = Free.runFC(calcScript[CalcScript](opr))(or(InteractConsole, CalcInterp))  println(uid)  println(opr)  println(sum)}


不过这个例子还不算是一个完整的程序。我们印象中的完整应用应该还要加上交互循环、错误提示等等。我们能不能用FP方式来完善这个例子呢?先说循环吧(looping):FP循环不就是递归嘛(recursion),实在不行就试试Trampoline。关于程序的流程控制:我们可以在节点之间传递一个状态,代表下一步的操作:

    trait NextStep  //状态: 下一步操作    case object Login extends NextStep  //登录,用户信息验证    case class End(msg: String) extends NextStep  //正常结束退出    case class Opr(uid: String) extends NextStep  //计算操作选项及权限验证    case class Calc(uid: String, opr: String) extends NextStep //计算操作


现在我们可以编写一个函数来运算每一个步骤:

    def runStep(step: NextStep): Exception \/ NextStep = {      try {       step match {        case Login => {         Free.runFC(loginPrg)(or(InteractLogin, LoginInterp)).run(Passwords) match {           case "???" => End("Termination! Login failed").right           case uid: String => Opr(uid).right           case _ => End("Abnormal Termination! Unknown error.").right         }        }        case Opr(uid) =>          Free.runFC(accessScript[AccessScript](uid))(or(InteractPermission, PermissionInterp)).          run(AccessRights) match {            case "XXX" => Opr(uid).right            case opr: String => if (opr.toUpperCase.startsWith("Q")) End("End at user request。").right                                else Calc(uid,opr).right            case _ => End("Abnormal Termination! Unknown error.").right          }        case Calc(uid,opr) =>           println(Free.runFC(calcScript[CalcScript](opr))(or(InteractConsole, CalcInterp)))          Opr(uid).right       }      }      catch {         case e: Exception => e.left[NextStep]        }    }


在这个函数里我们增加了uid="XXX",opr.toUpperCase.startWith("Q")以及opr="???"这几个状态。需要调整一下AccessScript和LoginScript:

  object FreeProgs {    import FreeFunctions._    import FreeInteract._    import FreeLogin._    import FreePermission._    import FreeCalculator._    def freeCMonad[S[_]] = Free.freeMonad[({type l[x] = Coyoneda[S,x]})#l]    def loginScript[F[_]](implicit I: Interacts[F], L: Logins[F]) = {      import I._      import L._      for {        uid <- ask("ya id:",identity)        pwd <- ask("password:",identity)        login <- login(uid,pwd)        _ <- if (login) tell("ya in, ya lucky bastard!")                else tell("geta fk outa here!")        usr <- if (login) freeCMonad[F].point(uid)                else freeCMonad[F].point("???")      } yield usr    }    def accessScript[F[_]](uid: String)(implicit I: Interacts[F], P: Permissions[F]) = {      import I._      import P._      for {        inp <- ask("votiu vangto do?",identity)        cando <- if (inp.toUpperCase.startsWith("Q")) freeCMonad[F].point(true) else hasPermission(uid,inp)        _ <- if (cando) freeCMonad[F].point("")                else tell("na na na, cant do that!")           opr <- if (cando) freeCMonad[F].point(inp)                else freeCMonad[F].point("XXX")      } yield opr           }    def calcScript[F[_]](opr: String)(implicit I: Interacts[F], C: Calculators[F]) = {      import I._;import C._;      for {        op1 <- ask("fus num:", _.toInt)        op2 <- ask("nx num:", _.toInt)        result <- calc(opr,op1,op2)      } yield result    }


然后我们可以进行循环互动了:

    import scala.annotation.tailrec    @tailrec    def whileRun(state: Exception \/ NextStep): Unit = state match {      case \/-(End(msg)) => println(msg)      case \/-(nextStep: NextStep) => whileRun(runStep(nextStep))      case -\/(e) => println(e)      case _ => println("Unknown exception!")    }


这是一个尾递归算法(tail recursion)。测试运行 :

object FreeProgram extends App {  import Modules._  import FreeRunner._  whileRun(Login.right)}

下面是测试结果:

ya id:Tigerpassword:1234ya in, man!votiu vangto do?Addfus num:12nx num:5got ya self a 17.votiu vangto do?23na na na, can't do that!votiu vangto do?Subfus num:23nx num:5got ya self a 18.votiu vangto do?quitEnd at user request。

ya id:Johnpassword:1234geta fk outa here!, you bastardTermination! Login failed


ya id:Johnpassword:0332ya in, man!votiu vangto do?Addna na na, can't do that!votiu vangto do?Mulfus num:3nx num:7got ya self a 21.votiu vangto do?Divfus num:10nx num:3got ya self a 3.votiu vangto do?Divfus num:12nx num:0Abnormal termination!java.lang.ArithmeticException: / by zero


我们也可以用Trampoline来循环运算这个示范:

    import scalaz.Free.Trampoline    import scalaz.Trampoline._    def runTrampoline(state: Exception \/ NextStep): Trampoline[Unit] = state match {      case \/-(End(msg)) => done(println(msg))      case \/-(nextStep: NextStep) => suspend(runTrampoline(runStep(nextStep)))      case -\/(e) => done({println("Abnormal termination!"); println(e)})      case _ => done(println("Unknown exception!"))    }

测试运算:

object FreeProgram extends App {  import Modules._  import FreeRunner._//  whileRun(Login.right)  runTrampoline(Login.right).run}


测试运算结果:
ya id:Tigerpassword:1234ya in, man!votiu vangto do?Subfus num:12nx num:15got ya self a -3.votiu vangto do?Mulna na na, can't do that!votiu vangto do?Addfus num:10nx num:5got ya self a 15.votiu vangto do?quitEnd at user request。


好了,下面是这个示范的完整源代码:

package run.demoimport scalaz._import Scalaz._import scala.language.higherKindsimport scala.language.implicitConversionsimport run.demo.Modules.FreeCalculator.CalcInterpobject Modules {  object FreeInteract {    trait Interact[+NextAct]    object Interact {      case class Ask[NextAct](prompt: String, onInput: String => NextAct) extends Interact[NextAct]      case class Tell[NextAct](msg: String, n: NextAct) extends Interact[NextAct]      implicit object interactFunctor extends Functor[Interact] {         def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ia match {           case Ask(p,onInput) => Ask(p, onInput andThen f)           case Tell(m,n) => Tell(m, f(n))         }      }     }    import Interact._    object InteractConsole extends (Interact ~> Id) {      def apply[A](ia: Interact[A]): Id[A] = ia match {        case Ask(p,onInput) => println(p); onInput(readLine)        case Tell(m, n) => println(m); n      }    }    import FreeLogin._    object InteractLogin extends (Interact ~> PasswordReader) {      def apply[A](ia: Interact[A]): PasswordReader[A] = ia match {        case Ask(p,onInput) => println(p); Reader(m => onInput(readLine))        case Tell(m, n) => println(m); Reader(m => n)      }    }    import FreePermission._    object InteractPermission extends(Interact ~> PermissionReader) {      def apply[A](ia: Interact[A]): PermissionReader[A] = ia match {        case Ask(p,onInput) => println(p);Reader(m => onInput(readLine))        case Tell(m,n) => println(m); Reader(m => n)      }    }  }  object FreeLogin {    trait UserLogin[+A]    object UserLogin {      case class Login(uid: String, pswd: String) extends UserLogin[Boolean]    }     import UserLogin._    import Dependencies._    type PasswordReader[A] = Reader[PasswordControl, A]    object LoginInterp extends (UserLogin ~> PasswordReader) {      def apply[A](la: UserLogin[A]): PasswordReader[A] = la match {        case Login(uid,pswd) => Reader(m => m.matchPassword(uid, pswd))      }    }  }  object FreePermission {    trait Permission[+A]    object Permission {      case class HasPermission(uid: String, opr: String) extends Permission[Boolean]    }    import Dependencies._    import Permission._    type PermissionReader[A] = Reader[PermissionControl,A]    object PermissionInterp extends (Permission ~> PermissionReader) {      def apply[A](pa: Permission[A]): PermissionReader[A] = pa match {        case HasPermission(uid,opr) => Reader {m => m.matchPermission(uid, opr)}      }    }  }  object FreeCalculator {    trait Calculator[+A]    object Calculator {      case class Calc(opr: String, lop: Int, rop: Int) extends Calculator[Int]    }    import Calculator._    object CalcInterp extends (Calculator ~> Id) {      def apply[A](ca: Calculator[A]): Id[A] = ca match {        case Calc(opr,op1,op2) => opr.toUpperCase match {          case "ADD" => op1 + op2          case "SUB" => op1 - op2          case "MUL" => op1 * op2          case "DIV" => op1 / op2        }      }    }  }  object FreeFunctions {    import FreeInteract._    import Interact._    import FreeLogin._    import UserLogin._    import FreePermission._    import Permission._    import FreeCalculator._    import Calculator._    def lift[F[_],G[_],A](fa: F[A])(implicit I: Inject[F,G]): Free.FreeC[G,A] =        Free.liftFC(I.inj(fa))     class Interacts[G[_]](implicit I: Inject[Interact,G]) {      def ask[A](prompt: String, onInput: String => A) = Free.liftFC(I.inj(Ask(prompt, onInput)))      def tell[A](msg: String) = Free.liftFC(I.inj(Tell(msg, ())))    }    object Interacts {      implicit def instance[F[_]](implicit I: Inject[Interact,F]) = new Interacts[F]    }    class Logins[G[_]](implicit I: Inject[UserLogin,G]) {      def login(uid: String, pswd: String) = lift(Login(uid,pswd))    }    object Logins {      implicit def instance[F[_]](implicit I: Inject[UserLogin,F]) = new Logins[F]    }    class Permissions[G[_]](implicit I: Inject[Permission,G]) {      def hasPermission(uid: String, opr: String) = lift(HasPermission(uid,opr))    }    object Permissions {      implicit def instance[F[_]](implicit I: Inject[Permission,F]) = new Permissions[F]    }    class Calculators[G[_]](implicit I: Inject[Calculator,G]) {      def calc(opr: String, op1: Int, op2: Int) = lift(Calc(opr,op1,op2))    }    object Calculators {      implicit def instance[F[_]](implicit I: Inject[Calculator,F]) = new Calculators[F]    }    def or[F[_],H[_],G[_]](fg: F ~> G, hg: H ~> G): ({type l[x] = Coproduct[F,H,x]})#l ~> G =      new (({type l[x] = Coproduct[F,H,x]})#l ~> G) {       def apply[A](ca: Coproduct[F,H,A]): G[A] = ca.run match {         case -\/(x) => fg(x)         case \/-(y) => hg(y)       }    }  }  object FreeProgs {    import FreeFunctions._    import FreeInteract._    import FreeLogin._    import FreePermission._    import FreeCalculator._    def freeCMonad[S[_]] = Free.freeMonad[({type l[x] = Coyoneda[S,x]})#l]    def loginScript[F[_]](implicit I: Interacts[F], L: Logins[F]) = {      import I._      import L._      for {        uid <- ask("ya id:",identity)        pwd <- ask("password:",identity)        login <- login(uid,pwd)        _ <- if (login) tell("ya in, man!")                else tell("geta fk outa here!, you bastard")        usr <- if (login) freeCMonad[F].point(uid)                else freeCMonad[F].point("???")      } yield usr    }    def accessScript[F[_]](uid: String)(implicit I: Interacts[F], P: Permissions[F]) = {      import I._      import P._      for {        inp <- ask("votiu vangto do?",identity)        cando <- if (inp.toUpperCase.startsWith("Q")) freeCMonad[F].point(true) else hasPermission(uid,inp)        _ <- if (cando) freeCMonad[F].point("")                else tell("na na na, can't do that!")           opr <- if (cando) freeCMonad[F].point(inp)                else freeCMonad[F].point("XXX")      } yield opr           }    def calcScript[F[_]](opr: String)(implicit I: Interacts[F], C: Calculators[F]) = {      import I._;import C._;      for {        op1 <- ask("fus num:", _.toInt)        op2 <- ask("nx num:", _.toInt)        result <- calc(opr,op1,op2)      } yield result    }    type LoginScript[A] = Coproduct[Interact, UserLogin, A]    type CalcScript[A] = Coproduct[Interact, Calculator, A]    type AccessScript[A] = Coproduct[Interact, Permission, A]    val accessPrg = accessScript[AccessScript] _    val loginPrg = loginScript[LoginScript]    val calcPrg = calcScript[CalcScript] _     }  object FreeRunner {    import FreeInteract._    import FreeLogin._    import FreePermission._    import FreeFunctions._    import FreeProgs._    import Dependencies._    trait NextStep  //状态: 下一步操作    case object Login extends NextStep  //登录,用户信息验证    case class End(msg: String) extends NextStep  //正常结束退出    case class Opr(uid: String) extends NextStep  //计算操作选项及权限验证    case class Calc(uid: String, opr: String) extends NextStep //计算操作    object Passwords extends PasswordControl {      val pswdMap = Map (       "Tiger" -> "1234",       "John" -> "0332"      )      def matchPassword(uid: String, pswd: String) = pswdMap.getOrElse(uid, pswd+"!") === pswd    }       object AccessRights extends PermissionControl {       val permMap = Map (         "Tiger" -> List("Add","Sub"),         "John" -> List("Mul","Div")       )       def matchPermission(uid: String, opr: String) = permMap.getOrElse(uid, List()).exists { _ === opr}    }        def runStep(step: NextStep): Exception \/ NextStep = {      try {       step match {        case Login => {         Free.runFC(loginPrg)(or(InteractLogin, LoginInterp)).run(Passwords) match {           case "???" => End("Termination! Login failed").right           case uid: String => Opr(uid).right           case _ => End("Abnormal Termination! Unknown error.").right         }        }        case Opr(uid) =>          Free.runFC(accessScript[AccessScript](uid))(or(InteractPermission, PermissionInterp)).          run(AccessRights) match {            case "XXX" => Opr(uid).right            case opr: String => if (opr.toUpperCase.startsWith("Q")) End("End at user request。").right                                else Calc(uid,opr).right            case _ => End("Abnormal Termination! Unknown error.").right          }        case Calc(uid,opr) =>           println(s"got ya self a ${Free.runFC(calcScript[CalcScript](opr))(or(InteractConsole, CalcInterp))}.")          Opr(uid).right       }      }      catch {         case e: Exception => e.left[NextStep]        }    }    import scala.annotation.tailrec    @tailrec    def whileRun(state: Exception \/ NextStep): Unit = state match {      case \/-(End(msg)) => println(msg)      case \/-(nextStep: NextStep) => whileRun(runStep(nextStep))      case -\/(e) => println("Abnormal termination!"); println(e)      case _ => println("Unknown exception!")    }    import scalaz.Free.Trampoline    import scalaz.Trampoline._    def runTrampoline(state: Exception \/ NextStep): Trampoline[Unit] = state match {      case \/-(End(msg)) => done(println(msg))      case \/-(nextStep: NextStep) => suspend(runTrampoline(runStep(nextStep)))      case -\/(e) => done({println("Abnormal termination!"); println(e)})      case _ => done(println("Unknown exception!"))    }  }}object Dependencies {  trait PasswordControl {    val pswdMap: Map[String,String]    def matchPassword(uid: String, pswd: String): Boolean  }  trait PermissionControl {    val permMap: Map[String,List[String]]    def matchPermission(uid: String, operation: String): Boolean  }}object FreeProgram extends App {  import Modules._  import FreeRunner._//  whileRun(Login.right)  runTrampoline(Login.right).run}


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 关于头发头顶头发少怎么办 每天掉100根头发怎么办 小孩嘴巴烂了该怎么办 小孩吃烧烤嘴巴肿好大该怎么办 脸特别瘦的人怎么办 怀孕宝宝太懒了怎么办 奶油胶手机壳脏了怎么办 为什么电脑桌面壁纸变成黑色怎么办 仓鼠妈妈老忘了宝宝怎么办 木工三排多轴钻床气缸坏了怎么办? 水溶笔洗不掉怎么办 针管笔没有墨了怎么办 二岁小儿不爱吃饭怎么办 狗狗一直挠痒痒怎么办 手被铅笔扎了怎么办 小孩智力轻度低下该怎么办 发侮辱人的信息怎么办 宋民国表情我能怎么办 苹果手机微信弄成听筒模式怎么办 我的小可爱丢了怎么办 法斗犬老是皮肤病看也看不好怎么办 项链水晶不亮了怎么办 美甲画画手抖怎么办 微信中的表情符号不全怎么办 球球大作战找不到团战服务器怎么办 小孩的腰有点弯怎么办 小朋友不听话被老师罚站怎么办 幼儿园小朋友被老师罚站怎么办 生完小孩弯腰驼背怎么办 小孩爱捡垃圾是怎么办 腰扭了不敢弯腰怎么办 小人狗在背后骂我怎么办 微信表情缺失了怎么办 动图过大 微信 怎么办 我能怎么办图片带字 我该怎么办图片带字 学化妆找不到模特练妆怎么办? cf进房间闪退怎么办 手游cf账号封了怎么办 大门牙缺了一块怎么办 缺了一颗牙齿怎么办