Scalaz(39)- Free :a real monadic program详解编程语言

   一直感觉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)以及依赖注入、运算、结果等等先摆出来,然后再逐段分析说明:

  1 package run.demo 
  2 import scalaz._ 
  3 import Scalaz._ 
  4 import scala.language.higherKinds 
  5 import scala.language.implicitConversions 
  6 import run.demo.Modules.FreeCalculator.CalcInterp 
  7  
  8 object Modules { 
  9   object FreeInteract { 
 10     trait Interact[+NextAct] 
 11     object Interact { 
 12       case class Ask[NextAct](prompt: String, onInput: String => NextAct) extends Interact[NextAct] 
 13       case class Tell[NextAct](msg: String, n: NextAct) extends Interact[NextAct] 
 14       implicit object interactFunctor extends Functor[Interact] { 
 15          def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ia match { 
 16            case Ask(p,onInput) => Ask(p, onInput andThen f) 
 17            case Tell(m,n) => Tell(m, f(n)) 
 18          } 
 19       }  
 20     } 
 21     import Interact._ 
 22     object InteractConsole extends (Interact ~> Id) { 
 23       def apply[A](ia: Interact[A]): Id[A] = ia match { 
 24         case Ask(p,onInput) => println(p); onInput(readLine) 
 25         case Tell(m, n) => println(m); n 
 26       } 
 27     } 
 28     import FreeLogin._ 
 29     object InteractLogin extends (Interact ~> PasswordReader) { 
 30       def apply[A](ia: Interact[A]): PasswordReader[A] = ia match { 
 31         case Ask(p,onInput) => println(p); Reader(m => onInput(readLine)) 
 32         case Tell(m, n) => println(m); Reader(m => n) 
 33       } 
 34     } 
 35     import FreePermission._ 
 36     object InteractPermission extends(Interact ~> PermissionReader) { 
 37       def apply[A](ia: Interact[A]): PermissionReader[A] = ia match { 
 38         case Ask(p,onInput) => println(p);Reader(m => onInput(readLine)) 
 39         case Tell(m,n) => println(m); Reader(m => n) 
 40       } 
 41     } 
 42   } 
 43   object FreeLogin { 
 44     trait UserLogin[+A] 
 45     object UserLogin { 
 46       case class Login(uid: String, pswd: String) extends UserLogin[Boolean] 
 47     }  
 48     import UserLogin._ 
 49     import Dependencies._ 
 50     type PasswordReader[A] = Reader[PasswordControl, A] 
 51     object LoginInterp extends (UserLogin ~> PasswordReader) { 
 52       def apply[A](la: UserLogin[A]): PasswordReader[A] = la match { 
 53         case Login(uid,pswd) => Reader(m => m.matchPassword(uid, pswd)) 
 54       } 
 55     } 
 56   } 
 57   object FreePermission { 
 58     trait Permission[+A] 
 59     object Permission { 
 60       case class HasPermission(uid: String, opr: String) extends Permission[Boolean] 
 61     } 
 62     import Dependencies._ 
 63     import Permission._ 
 64     type PermissionReader[A] = Reader[PermissionControl,A] 
 65     object PermissionInterp extends (Permission ~> PermissionReader) { 
 66       def apply[A](pa: Permission[A]): PermissionReader[A] = pa match { 
 67         case HasPermission(uid,opr) => Reader {m => m.matchPermission(uid, opr)} 
 68       } 
 69     } 
 70   } 
 71   object FreeCalculator { 
 72     trait Calculator[+A] 
 73     object Calculator { 
 74       case class Calc(opr: String, lop: Int, rop: Int) extends Calculator[Int] 
 75     } 
 76     import Calculator._ 
 77     object CalcInterp extends (Calculator ~> Id) { 
 78       def apply[A](ca: Calculator[A]): Id[A] = ca match { 
 79         case Calc(opr,op1,op2) => opr.toUpperCase match { 
 80           case "ADD" => op1 + op2 
 81           case "SUB" => op1 - op2 
 82           case "MUL" => op1 * op2 
 83           case "DIV" => op1 / op2 
 84         } 
 85       } 
 86     } 
 87   } 
 88   object FreeFunctions { 
 89     import FreeInteract._ 
 90     import Interact._ 
 91     import FreeLogin._ 
 92     import UserLogin._ 
 93     import FreePermission._ 
 94     import Permission._ 
 95     import FreeCalculator._ 
 96     import Calculator._ 
 97     def lift[F[_],G[_],A](fa: F[A])(implicit I: Inject[F,G]): Free.FreeC[G,A] =  
 98        Free.liftFC(I.inj(fa))  
 99     class Interacts[G[_]](implicit I: Inject[Interact,G]) { 
100       def ask[A](prompt: String, onInput: String => A) = Free.liftFC(I.inj(Ask(prompt, onInput))) 
101       def tell[A](msg: String) = Free.liftFC(I.inj(Tell(msg, ()))) 
102     } 
103     object Interacts { 
104       implicit def instance[F[_]](implicit I: Inject[Interact,F]) = new Interacts[F] 
105     } 
106     class Logins[G[_]](implicit I: Inject[UserLogin,G]) { 
107       def login(uid: String, pswd: String) = lift(Login(uid,pswd)) 
108     } 
109     object Logins { 
110       implicit def instance[F[_]](implicit I: Inject[UserLogin,F]) = new Logins[F] 
111     } 
112     class Permissions[G[_]](implicit I: Inject[Permission,G]) { 
113       def hasPermission(uid: String, opr: String) = lift(HasPermission(uid,opr)) 
114     } 
115     object Permissions { 
116       implicit def instance[F[_]](implicit I: Inject[Permission,F]) = new Permissions[F] 
117     } 
118     class Calculators[G[_]](implicit I: Inject[Calculator,G]) { 
119       def calc(opr: String, op1: Int, op2: Int) = lift(Calc(opr,op1,op2)) 
120     } 
121     object Calculators { 
122       implicit def instance[F[_]](implicit I: Inject[Calculator,F]) = new Calculators[F] 
123     } 
124     def or[F[_],H[_],G[_]](fg: F ~> G, hg: H ~> G): ({type l[x] = Coproduct[F,H,x]})#l ~> G = 
125       new (({type l[x] = Coproduct[F,H,x]})#l ~> G) { 
126        def apply[A](ca: Coproduct[F,H,A]): G[A] = ca.run match { 
127          case -//(x) => fg(x) 
128          case //-(y) => hg(y) 
129        } 
130     } 
131   } 
132   object FreeProgs { 
133     import FreeFunctions._ 
134     import FreeInteract._ 
135     import FreeLogin._ 
136     import FreePermission._ 
137     import FreeCalculator._ 
138     def freeCMonad[S[_]] = Free.freeMonad[({type l[x] = Coyoneda[S,x]})#l] 
139     def loginScript[F[_]](implicit I: Interacts[F], L: Logins[F]) = { 
140       import I._ 
141       import L._ 
142       for { 
143         uid <- ask("ya id:",identity) 
144         pwd <- ask("password:",identity) 
145         login <- login(uid,pwd) 
146         _ <- if (login) tell("ya in, ya lucky bastard!") 
147                 else tell("geta fk outa here!") 
148         usr <- if (login) freeCMonad[F].point(uid)  
149                else freeCMonad[F].point("???") 
150       } yield usr 
151     } 
152     def accessScript[F[_]](uid: String)(implicit I: Interacts[F], P: Permissions[F]) = { 
153       import I._ 
154       import P._ 
155       for { 
156         inp <- ask("votiu vangto do?",identity) 
157         cando <- hasPermission(uid,inp) 
158         _ <- if (cando) tell("ok, go on ...") 
159                 else tell("na na na, cant do that!")    
160         opr <- if (cando) freeCMonad[F].point(inp)  
161                else freeCMonad[F].point("XXX") 
162       } yield opr 
163         
164     } 
165  
166     def calcScript[F[_]](opr: String)(implicit I: Interacts[F], C: Calculators[F]) = { 
167       import I._;import C._; 
168       for { 
169         op1 <- ask("fus num:", _.toInt) 
170         op2 <- ask("nx num:", _.toInt) 
171         result <- calc(opr,op1,op2) 
172       } yield result 
173     } 
174  
175     type LoginScript[A] = Coproduct[Interact, UserLogin, A] 
176     type CalcScript[A] = Coproduct[Interact, Calculator, A] 
177     type AccessScript[A] = Coproduct[Interact, Permission, A] 
178     val accessPrg = accessScript[AccessScript] _ 
179     val loginPrg = loginScript[LoginScript] 
180     val calcPrg = calcScript[CalcScript] _ 
181   } 
182 } 
183 object Dependencies { 
184   trait PasswordControl { 
185     val pswdMap: Map[String,String] 
186     def matchPassword(uid: String, pswd: String): Boolean 
187   } 
188   trait PermissionControl { 
189     val permMap: Map[String,List[String]] 
190     def matchPermission(uid: String, operation: String): Boolean 
191   } 
192 } 
193 object FreeProgram extends App { 
194   import Modules._ 
195   import FreeInteract._ 
196   import FreeLogin._ 
197   import FreePermission._ 
198   import FreeFunctions._ 
199   import FreeProgs._ 
200   import Dependencies._ 
201   object Passwords extends PasswordControl { 
202      val pswdMap = Map ( 
203        "Tiger" -> "1234", 
204        "John" -> "0332" 
205      ) 
206      def matchPassword(uid: String, pswd: String) = pswdMap.getOrElse(uid, pswd+"!") === pswd 
207   } 
208   object AccessRights extends PermissionControl { 
209      val permMap = Map ( 
210        "Tiger" -> List("Add","Sub"), 
211        "John" -> List("Mul","Div") 
212      ) 
213      def matchPermission(uid: String, opr: String) = permMap.getOrElse(uid, List()).exists { _ === opr} 
214   } 
215    
216   val uid = Free.runFC(loginPrg)(or(InteractLogin, LoginInterp)).run(Passwords) 
217   val opr = Free.runFC(accessScript[AccessScript](uid))(or(InteractPermission, PermissionInterp)).run(AccessRights) 
218   val sum = Free.runFC(calcScript[CalcScript](opr))(or(InteractConsole, CalcInterp)) 
219   println(uid) 
220   println(opr) 
221   println(sum) 
222 } 
223 //测试运算结果 
224 ya id: 
225 Tiger 
226 password: 
227 1234 
228 ya in, ya lucky bastard! 
229 votiu vangto do? 
230 Add 
231 ok, go on ... 
232 fus num: 
233 3 
234 nx num: 
235 7 
236 Tiger 
237 Add 
238 10

看起来好像费了老大劲就做那么点事。但如果我们按照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:

 1    trait Interact[+NextAct] 
 2     object Interact { 
 3       case class Ask[NextAct](prompt: String, onInput: String => NextAct) extends Interact[NextAct] 
 4       case class Tell[NextAct](msg: String, n: NextAct) extends Interact[NextAct] 
 5       implicit object interactFunctor extends Functor[Interact] { 
 6          def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ia match { 
 7            case Ask(p,onInput) => Ask(p, onInput andThen f) 
 8            case Tell(m,n) => Tell(m, f(n)) 
 9          } 
10       }  
11     } 
12  

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

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

 1   object FreeFunctions { 
 2     import FreeInteract._ 
 3     import Interact._ 
 4     import FreeLogin._ 
 5     import UserLogin._ 
 6     import FreePermission._ 
 7     import Permission._ 
 8     import FreeCalculator._ 
 9     import Calculator._ 
10     def lift[F[_],G[_],A](fa: F[A])(implicit I: Inject[F,G]): Free.FreeC[G,A] =  
11        Free.liftFC(I.inj(fa))  
12     class Interacts[G[_]](implicit I: Inject[Interact,G]) { 
13       def ask[A](prompt: String, onInput: String => A) = Free.liftFC(I.inj(Ask(prompt, onInput))) 
14       def tell[A](msg: String) = Free.liftFC(I.inj(Tell(msg, ()))) 
15     } 
16     object Interacts { 
17       implicit def instance[F[_]](implicit I: Inject[Interact,F]) = new Interacts[F] 
18     } 
19     class Logins[G[_]](implicit I: Inject[UserLogin,G]) { 
20       def login(uid: String, pswd: String) = lift(Login(uid,pswd)) 
21     } 
22     object Logins { 
23       implicit def instance[F[_]](implicit I: Inject[UserLogin,F]) = new Logins[F] 
24     } 
25     class Permissions[G[_]](implicit I: Inject[Permission,G]) { 
26       def hasPermission(uid: String, opr: String) = lift(HasPermission(uid,opr)) 
27     } 
28     object Permissions { 
29       implicit def instance[F[_]](implicit I: Inject[Permission,F]) = new Permissions[F] 
30     } 
31     class Calculators[G[_]](implicit I: Inject[Calculator,G]) { 
32       def calc(opr: String, op1: Int, op2: Int) = lift(Calc(opr,op1,op2)) 
33     } 
34     object Calculators { 
35       implicit def instance[F[_]](implicit I: Inject[Calculator,F]) = new Calculators[F] 
36     } 
37     def or[F[_],H[_],G[_]](fg: F ~> G, hg: H ~> G): ({type l[x] = Coproduct[F,H,x]})#l ~> G = 
38       new (({type l[x] = Coproduct[F,H,x]})#l ~> G) { 
39        def apply[A](ca: Coproduct[F,H,A]): G[A] = ca.run match { 
40          case -//(x) => fg(x) 
41          case //-(y) => hg(y) 
42        } 
43     } 
44   }

在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来实现这些语句集的联合:

1     type LoginScript[A] = Coproduct[Interact, UserLogin, A] 
2     type CalcScript[A] = Coproduct[Interact, Calculator, A] 
3     type AccessScript[A] = Coproduct[Interact, Permission, A] 
4     val accessPrg = accessScript[AccessScript] _ 
5     val loginPrg = loginScript[LoginScript] 
6     val calcPrg = calcScript[CalcScript] _

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

1     type F0[A] = Coproduct[Interact,UserLogin,A] 
2     type F1[A] = Coproduct[Permission,F0,A] 
3     type F2[A] = Coproduct[Calculator,F1,A] 
4     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可能成为将来的一种主要编程模式,所以必须想办法解决多语句集联合使用的问题。不过我们把这个放到以后再说。

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

 1  object FreeProgs { 
 2     import FreeFunctions._ 
 3     import FreeInteract._ 
 4     import FreeLogin._ 
 5     import FreePermission._ 
 6     import FreeCalculator._ 
 7     def freeCMonad[S[_]] = Free.freeMonad[({type l[x] = Coyoneda[S,x]})#l] 
 8     def loginScript[F[_]](implicit I: Interacts[F], L: Logins[F]) = { 
 9       import I._ 
10       import L._ 
11       for { 
12         uid <- ask("ya id:",identity) 
13         pwd <- ask("password:",identity) 
14         login <- login(uid,pwd) 
15         _ <- if (login) tell("ya in, ya lucky bastard!") 
16                 else tell("geta fk outa here!") 
17         usr <- if (login) freeCMonad[F].point(uid)  
18                else freeCMonad[F].point("???") 
19       } yield uid 
20     } 
21     def accessScript[F[_]](uid: String)(implicit I: Interacts[F], P: Permissions[F]) = { 
22       import I._ 
23       import P._ 
24       for { 
25         inp <- ask("votiu vangto do?",identity) 
26         cando <- hasPermission(uid,inp) 
27         _ <- if (cando) tell("ok, go on ...") 
28                 else tell("na na na, cant do that!")    
29         opr <- if (cando) freeCMonad[F].point(inp)  
30                else freeCMonad[F].point("XXX") 
31       } yield inp 
32         
33     } 
34  
35     def calcScript[F[_]](opr: String)(implicit I: Interacts[F], C: Calculators[F]) = { 
36       import I._;import C._; 
37       for { 
38         op1 <- ask("fus num:", _.toInt) 
39         op2 <- ask("nx num:", _.toInt) 
40         result <- calc(opr,op1,op2) 
41       } yield result 
42     } 
43  
44     type LoginScript[A] = Coproduct[Interact, UserLogin, A] 
45     type CalcScript[A] = Coproduct[Interact, Calculator, A] 
46     type AccessScript[A] = Coproduct[Interact, Permission, A] 
47     val accessPrg = accessScript[AccessScript] _ 
48     val loginPrg = loginScript[LoginScript] 
49     val calcPrg = calcScript[CalcScript] _    
50   }

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

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

 1 object Dependencies { 
 2   trait PasswordControl { 
 3     val pswdMap: Map[String,String] 
 4     def matchPassword(uid: String, pswd: String): Boolean 
 5   } 
 6   trait PermissionControl { 
 7     val permMap: Map[String,List[String]] 
 8     def matchPermission(uid: String, operation: String): Boolean 
 9   } 
10 }

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

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

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

 1     object InteractConsole extends (Interact ~> Id) { 
 2       def apply[A](ia: Interact[A]): Id[A] = ia match { 
 3         case Ask(p,onInput) => println(p); onInput(readLine) 
 4         case Tell(m, n) => println(m); n 
 5       } 
 6     } 
 7     import FreeLogin._ 
 8     object InteractLogin extends (Interact ~> PasswordReader) { 
 9       def apply[A](ia: Interact[A]): PasswordReader[A] = ia match { 
10         case Ask(p,onInput) => println(p); Reader(m => onInput(readLine)) 
11         case Tell(m, n) => println(m); Reader(m => n) 
12       } 
13     } 
14     import FreePermission._ 
15     object InteractPermission extends(Interact ~> PermissionReader) { 
16       def apply[A](ia: Interact[A]): PermissionReader[A] = ia match { 
17         case Ask(p,onInput) => println(p);Reader(m => onInput(readLine)) 
18         case Tell(m,n) => println(m); Reader(m => n) 
19       } 
20     }

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

1     def or[F[_],H[_],G[_]](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 -//(x) => fg(x) 
5          case //-(y) => hg(y) 
6        }

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

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

 1 object FreeProgram extends App { 
 2   import Modules._ 
 3   import FreeInteract._ 
 4   import FreeLogin._ 
 5   import FreePermission._ 
 6   import FreeFunctions._ 
 7   import FreeProgs._ 
 8   import Dependencies._ 
 9   object Passwords extends PasswordControl { 
10      val pswdMap = Map ( 
11        "Tiger" -> "1234", 
12        "John" -> "0332" 
13      ) 
14      def matchPassword(uid: String, pswd: String) = pswdMap.getOrElse(uid, pswd+"!") === pswd 
15   } 
16   object AccessRights extends PermissionControl { 
17      val permMap = Map ( 
18        "Tiger" -> List("Add","Sub"), 
19        "John" -> List("Mul","Div") 
20      ) 
21      def matchPermission(uid: String, opr: String) = permMap.getOrElse(uid, List()).exists { _ === opr} 
22   } 
23    
24   val uid = Free.runFC(loginPrg)(or(InteractLogin, LoginInterp)).run(Passwords) 
25   val opr = Free.runFC(accessScript[AccessScript](uid))(or(InteractPermission, PermissionInterp)).run(AccessRights) 
26   val sum = Free.runFC(calcScript[CalcScript](opr))(or(InteractConsole, CalcInterp)) 
27   println(uid) 
28   println(opr) 
29   println(sum) 
30 }

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

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

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

 1     def runStep(step: NextStep): Exception // NextStep = { 
 2       try { 
 3        step match { 
 4         case Login => { 
 5          Free.runFC(loginPrg)(or(InteractLogin, LoginInterp)).run(Passwords) match { 
 6            case "???" => End("Termination! Login failed").right 
 7            case uid: String => Opr(uid).right 
 8            case _ => End("Abnormal Termination! Unknown error.").right 
 9          } 
10         } 
11         case Opr(uid) => 
12           Free.runFC(accessScript[AccessScript](uid))(or(InteractPermission, PermissionInterp)). 
13           run(AccessRights) match { 
14             case "XXX" => Opr(uid).right 
15             case opr: String => if (opr.toUpperCase.startsWith("Q")) End("End at user request。").right 
16                                 else Calc(uid,opr).right 
17             case _ => End("Abnormal Termination! Unknown error.").right 
18           } 
19         case Calc(uid,opr) =>  
20           println(Free.runFC(calcScript[CalcScript](opr))(or(InteractConsole, CalcInterp))) 
21           Opr(uid).right 
22        } 
23       } 
24       catch { 
25          case e: Exception => e.left[NextStep]   
26       } 
27     }

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

 1   object FreeProgs { 
 2     import FreeFunctions._ 
 3     import FreeInteract._ 
 4     import FreeLogin._ 
 5     import FreePermission._ 
 6     import FreeCalculator._ 
 7     def freeCMonad[S[_]] = Free.freeMonad[({type l[x] = Coyoneda[S,x]})#l] 
 8     def loginScript[F[_]](implicit I: Interacts[F], L: Logins[F]) = { 
 9       import I._ 
10       import L._ 
11       for { 
12         uid <- ask("ya id:",identity) 
13         pwd <- ask("password:",identity) 
14         login <- login(uid,pwd) 
15         _ <- if (login) tell("ya in, ya lucky bastard!") 
16                 else tell("geta fk outa here!") 
17         usr <- if (login) freeCMonad[F].point(uid)  
18                else freeCMonad[F].point("???") 
19       } yield usr 
20     } 
21     def accessScript[F[_]](uid: String)(implicit I: Interacts[F], P: Permissions[F]) = { 
22       import I._ 
23       import P._ 
24       for { 
25         inp <- ask("votiu vangto do?",identity) 
26         cando <- if (inp.toUpperCase.startsWith("Q")) freeCMonad[F].point(true) else hasPermission(uid,inp) 
27         _ <- if (cando) freeCMonad[F].point("") 
28                 else tell("na na na, cant do that!")    
29         opr <- if (cando) freeCMonad[F].point(inp)  
30                else freeCMonad[F].point("XXX") 
31       } yield opr 
32         
33     } 
34  
35     def calcScript[F[_]](opr: String)(implicit I: Interacts[F], C: Calculators[F]) = { 
36       import I._;import C._; 
37       for { 
38         op1 <- ask("fus num:", _.toInt) 
39         op2 <- ask("nx num:", _.toInt) 
40         result <- calc(opr,op1,op2) 
41       } yield result 
42     }

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

1     import scala.annotation.tailrec 
2     @tailrec 
3     def whileRun(state: Exception // NextStep): Unit = state match { 
4       case //-(End(msg)) => println(msg) 
5       case //-(nextStep: NextStep) => whileRun(runStep(nextStep)) 
6       case -//(e) => println(e) 
7       case _ => println("Unknown exception!") 
8     }

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

1 object FreeProgram extends App { 
2   import Modules._ 
3   import FreeRunner._ 
4   whileRun(Login.right) 
5 }

下面是测试结果:

ya id: 
Tiger 
password: 
1234 
ya in, man! 
votiu vangto do? 
Add 
fus num: 
12 
nx num: 
5 
got ya self a 17. 
votiu vangto do? 
23 
na na na, can't do that! 
votiu vangto do? 
Sub 
fus num: 
23 
nx num: 
5 
got ya self a 18. 
votiu vangto do? 
quit 
End at user request。
ya id: 
John 
password: 
1234 
geta fk outa here!, you bastard 
Termination! Login failed
ya id: 
John 
password: 
0332 
ya in, man! 
votiu vangto do? 
Add 
na na na, can't do that! 
votiu vangto do? 
Mul 
fus num: 
3 
nx num: 
7 
got ya self a 21. 
votiu vangto do? 
Div 
fus num: 
10 
nx num: 
3 
got ya self a 3. 
votiu vangto do? 
Div 
fus num: 
12 
nx num: 
0 
Abnormal termination! 
java.lang.ArithmeticException: / by zero

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

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

测试运算:

1 object FreeProgram extends App { 
2   import Modules._ 
3   import FreeRunner._ 
4 //  whileRun(Login.right) 
5   runTrampoline(Login.right).run             
6 }

测试运算结果:

 

ya id: 
Tiger 
password: 
1234 
ya in, man! 
votiu vangto do? 
Sub 
fus num: 
12 
nx num: 
15 
got ya self a -3. 
votiu vangto do? 
Mul 
na na na, can't do that! 
votiu vangto do? 
Add 
fus num: 
10 
nx num: 
5 
got ya self a 15. 
votiu vangto do? 
quit 
End at user request。

 

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

 

  1 package run.demo 
  2 import scalaz._ 
  3 import Scalaz._ 
  4 import scala.language.higherKinds 
  5 import scala.language.implicitConversions 
  6 import run.demo.Modules.FreeCalculator.CalcInterp 
  7  
  8 object Modules { 
  9   object FreeInteract { 
 10     trait Interact[+NextAct] 
 11     object Interact { 
 12       case class Ask[NextAct](prompt: String, onInput: String => NextAct) extends Interact[NextAct] 
 13       case class Tell[NextAct](msg: String, n: NextAct) extends Interact[NextAct] 
 14       implicit object interactFunctor extends Functor[Interact] { 
 15          def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ia match { 
 16            case Ask(p,onInput) => Ask(p, onInput andThen f) 
 17            case Tell(m,n) => Tell(m, f(n)) 
 18          } 
 19       }  
 20     } 
 21     import Interact._ 
 22     object InteractConsole extends (Interact ~> Id) { 
 23       def apply[A](ia: Interact[A]): Id[A] = ia match { 
 24         case Ask(p,onInput) => println(p); onInput(readLine) 
 25         case Tell(m, n) => println(m); n 
 26       } 
 27     } 
 28     import FreeLogin._ 
 29     object InteractLogin extends (Interact ~> PasswordReader) { 
 30       def apply[A](ia: Interact[A]): PasswordReader[A] = ia match { 
 31         case Ask(p,onInput) => println(p); Reader(m => onInput(readLine)) 
 32         case Tell(m, n) => println(m); Reader(m => n) 
 33       } 
 34     } 
 35     import FreePermission._ 
 36     object InteractPermission extends(Interact ~> PermissionReader) { 
 37       def apply[A](ia: Interact[A]): PermissionReader[A] = ia match { 
 38         case Ask(p,onInput) => println(p);Reader(m => onInput(readLine)) 
 39         case Tell(m,n) => println(m); Reader(m => n) 
 40       } 
 41     } 
 42   } 
 43   object FreeLogin { 
 44     trait UserLogin[+A] 
 45     object UserLogin { 
 46       case class Login(uid: String, pswd: String) extends UserLogin[Boolean] 
 47     }  
 48     import UserLogin._ 
 49     import Dependencies._ 
 50     type PasswordReader[A] = Reader[PasswordControl, A] 
 51     object LoginInterp extends (UserLogin ~> PasswordReader) { 
 52       def apply[A](la: UserLogin[A]): PasswordReader[A] = la match { 
 53         case Login(uid,pswd) => Reader(m => m.matchPassword(uid, pswd)) 
 54       } 
 55     } 
 56   } 
 57   object FreePermission { 
 58     trait Permission[+A] 
 59     object Permission { 
 60       case class HasPermission(uid: String, opr: String) extends Permission[Boolean] 
 61     } 
 62     import Dependencies._ 
 63     import Permission._ 
 64     type PermissionReader[A] = Reader[PermissionControl,A] 
 65     object PermissionInterp extends (Permission ~> PermissionReader) { 
 66       def apply[A](pa: Permission[A]): PermissionReader[A] = pa match { 
 67         case HasPermission(uid,opr) => Reader {m => m.matchPermission(uid, opr)} 
 68       } 
 69     } 
 70   } 
 71   object FreeCalculator { 
 72     trait Calculator[+A] 
 73     object Calculator { 
 74       case class Calc(opr: String, lop: Int, rop: Int) extends Calculator[Int] 
 75     } 
 76     import Calculator._ 
 77     object CalcInterp extends (Calculator ~> Id) { 
 78       def apply[A](ca: Calculator[A]): Id[A] = ca match { 
 79         case Calc(opr,op1,op2) => opr.toUpperCase match { 
 80           case "ADD" => op1 + op2 
 81           case "SUB" => op1 - op2 
 82           case "MUL" => op1 * op2 
 83           case "DIV" => op1 / op2 
 84         } 
 85       } 
 86     } 
 87   } 
 88   object FreeFunctions { 
 89     import FreeInteract._ 
 90     import Interact._ 
 91     import FreeLogin._ 
 92     import UserLogin._ 
 93     import FreePermission._ 
 94     import Permission._ 
 95     import FreeCalculator._ 
 96     import Calculator._ 
 97     def lift[F[_],G[_],A](fa: F[A])(implicit I: Inject[F,G]): Free.FreeC[G,A] =  
 98        Free.liftFC(I.inj(fa))  
 99     class Interacts[G[_]](implicit I: Inject[Interact,G]) { 
100       def ask[A](prompt: String, onInput: String => A) = Free.liftFC(I.inj(Ask(prompt, onInput))) 
101       def tell[A](msg: String) = Free.liftFC(I.inj(Tell(msg, ()))) 
102     } 
103     object Interacts { 
104       implicit def instance[F[_]](implicit I: Inject[Interact,F]) = new Interacts[F] 
105     } 
106     class Logins[G[_]](implicit I: Inject[UserLogin,G]) { 
107       def login(uid: String, pswd: String) = lift(Login(uid,pswd)) 
108     } 
109     object Logins { 
110       implicit def instance[F[_]](implicit I: Inject[UserLogin,F]) = new Logins[F] 
111     } 
112     class Permissions[G[_]](implicit I: Inject[Permission,G]) { 
113       def hasPermission(uid: String, opr: String) = lift(HasPermission(uid,opr)) 
114     } 
115     object Permissions { 
116       implicit def instance[F[_]](implicit I: Inject[Permission,F]) = new Permissions[F] 
117     } 
118     class Calculators[G[_]](implicit I: Inject[Calculator,G]) { 
119       def calc(opr: String, op1: Int, op2: Int) = lift(Calc(opr,op1,op2)) 
120     } 
121     object Calculators { 
122       implicit def instance[F[_]](implicit I: Inject[Calculator,F]) = new Calculators[F] 
123     } 
124     def or[F[_],H[_],G[_]](fg: F ~> G, hg: H ~> G): ({type l[x] = Coproduct[F,H,x]})#l ~> G = 
125       new (({type l[x] = Coproduct[F,H,x]})#l ~> G) { 
126        def apply[A](ca: Coproduct[F,H,A]): G[A] = ca.run match { 
127          case -//(x) => fg(x) 
128          case //-(y) => hg(y) 
129        } 
130     } 
131   } 
132   object FreeProgs { 
133     import FreeFunctions._ 
134     import FreeInteract._ 
135     import FreeLogin._ 
136     import FreePermission._ 
137     import FreeCalculator._ 
138     def freeCMonad[S[_]] = Free.freeMonad[({type l[x] = Coyoneda[S,x]})#l] 
139     def loginScript[F[_]](implicit I: Interacts[F], L: Logins[F]) = { 
140       import I._ 
141       import L._ 
142       for { 
143         uid <- ask("ya id:",identity) 
144         pwd <- ask("password:",identity) 
145         login <- login(uid,pwd) 
146         _ <- if (login) tell("ya in, man!") 
147                 else tell("geta fk outa here!, you bastard") 
148         usr <- if (login) freeCMonad[F].point(uid)  
149                else freeCMonad[F].point("???") 
150       } yield usr 
151     } 
152     def accessScript[F[_]](uid: String)(implicit I: Interacts[F], P: Permissions[F]) = { 
153       import I._ 
154       import P._ 
155       for { 
156         inp <- ask("votiu vangto do?",identity) 
157         cando <- if (inp.toUpperCase.startsWith("Q")) freeCMonad[F].point(true) else hasPermission(uid,inp) 
158         _ <- if (cando) freeCMonad[F].point("") 
159                 else tell("na na na, can't do that!")    
160         opr <- if (cando) freeCMonad[F].point(inp)  
161                else freeCMonad[F].point("XXX") 
162       } yield opr 
163         
164     } 
165  
166     def calcScript[F[_]](opr: String)(implicit I: Interacts[F], C: Calculators[F]) = { 
167       import I._;import C._; 
168       for { 
169         op1 <- ask("fus num:", _.toInt) 
170         op2 <- ask("nx num:", _.toInt) 
171         result <- calc(opr,op1,op2) 
172       } yield result 
173     } 
174  
175     type LoginScript[A] = Coproduct[Interact, UserLogin, A] 
176     type CalcScript[A] = Coproduct[Interact, Calculator, A] 
177     type AccessScript[A] = Coproduct[Interact, Permission, A] 
178     val accessPrg = accessScript[AccessScript] _ 
179     val loginPrg = loginScript[LoginScript] 
180     val calcPrg = calcScript[CalcScript] _    
181   } 
182   object FreeRunner { 
183     import FreeInteract._ 
184     import FreeLogin._ 
185     import FreePermission._ 
186     import FreeFunctions._ 
187     import FreeProgs._ 
188     import Dependencies._ 
189     trait NextStep  //状态: 下一步操作 
190     case object Login extends NextStep  //登录,用户信息验证 
191     case class End(msg: String) extends NextStep  //正常结束退出 
192     case class Opr(uid: String) extends NextStep  //计算操作选项及权限验证 
193     case class Calc(uid: String, opr: String) extends NextStep //计算操作 
194     object Passwords extends PasswordControl { 
195       val pswdMap = Map ( 
196        "Tiger" -> "1234", 
197        "John" -> "0332" 
198       ) 
199       def matchPassword(uid: String, pswd: String) = pswdMap.getOrElse(uid, pswd+"!") === pswd 
200     }    
201     object AccessRights extends PermissionControl { 
202        val permMap = Map ( 
203          "Tiger" -> List("Add","Sub"), 
204          "John" -> List("Mul","Div") 
205        ) 
206        def matchPermission(uid: String, opr: String) = permMap.getOrElse(uid, List()).exists { _ === opr} 
207     }     
208     def runStep(step: NextStep): Exception // NextStep = { 
209       try { 
210        step match { 
211         case Login => { 
212          Free.runFC(loginPrg)(or(InteractLogin, LoginInterp)).run(Passwords) match { 
213            case "???" => End("Termination! Login failed").right 
214            case uid: String => Opr(uid).right 
215            case _ => End("Abnormal Termination! Unknown error.").right 
216          } 
217         } 
218         case Opr(uid) => 
219           Free.runFC(accessScript[AccessScript](uid))(or(InteractPermission, PermissionInterp)). 
220           run(AccessRights) match { 
221             case "XXX" => Opr(uid).right 
222             case opr: String => if (opr.toUpperCase.startsWith("Q")) End("End at user request。").right 
223                                 else Calc(uid,opr).right 
224             case _ => End("Abnormal Termination! Unknown error.").right 
225           } 
226         case Calc(uid,opr) =>  
227           println(s"got ya self a ${Free.runFC(calcScript[CalcScript](opr))(or(InteractConsole, CalcInterp))}.") 
228           Opr(uid).right 
229        } 
230       } 
231       catch { 
232          case e: Exception => e.left[NextStep]   
233       } 
234     } 
235     import scala.annotation.tailrec 
236     @tailrec 
237     def whileRun(state: Exception // NextStep): Unit = state match { 
238       case //-(End(msg)) => println(msg) 
239       case //-(nextStep: NextStep) => whileRun(runStep(nextStep)) 
240       case -//(e) => println("Abnormal termination!"); println(e) 
241       case _ => println("Unknown exception!") 
242     } 
243     import scalaz.Free.Trampoline 
244     import scalaz.Trampoline._ 
245     def runTrampoline(state: Exception // NextStep): Trampoline[Unit] = state match { 
246       case //-(End(msg)) => done(println(msg)) 
247       case //-(nextStep: NextStep) => suspend(runTrampoline(runStep(nextStep))) 
248       case -//(e) => done({println("Abnormal termination!"); println(e)}) 
249       case _ => done(println("Unknown exception!")) 
250     } 
251   } 
252 } 
253 object Dependencies { 
254   trait PasswordControl { 
255     val pswdMap: Map[String,String] 
256     def matchPassword(uid: String, pswd: String): Boolean 
257   } 
258   trait PermissionControl { 
259     val permMap: Map[String,List[String]] 
260     def matchPermission(uid: String, operation: String): Boolean 
261   } 
262 } 
263 object FreeProgram extends App { 
264   import Modules._ 
265   import FreeRunner._ 
266 //  whileRun(Login.right) 
267   runTrampoline(Login.right).run             
268 }

 

 

 

 

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/12909.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论