Cats(3)- freeK-Free编程更轻松,Free programming with freeK详解编程语言

   在上一节我们讨论了通过Coproduct来实现DSL组合:用一些功能简单的基础DSL组合成符合大型多复杂功能应用的DSL。但是我们发现:cats在处理多层递归Coproduct结构时会出现编译问题。再就是Free编程是一个繁复的工作,容易出错,造成编程效率的低下。由于Free编程目前是函数式编程的主要方式(我个人认为),我们必须克服Free编程的效率问题。通过尝试,发现freeK可以作为一个很好的Free编程工具。freeK是个开源的泛函组件库,我们会在这次讨论里用freeK来完成上次讨论中以失败暂停的多层Coproduct Free程序。我们先试试Interact和Login两个混合DSL例子:

 1   object ADTs { 
 2     sealed trait Interact[+A] 
 3     object Interact { 
 4       case class Ask(prompt: String) extends Interact[String] 
 5       case class Tell(msg: String) extends Interact[Unit] 
 6     } 
 7     sealed trait Login[+A] 
 8     object Login { 
 9       case class Authenticate(uid: String, pwd: String) extends Login[Boolean] 
10     } 
11   } 
12   object DSLs { 
13     import ADTs._ 
14     import Interact._ 
15     import Login._ 
16     type PRG = Interact :|: Login :|: NilDSL 
17     val PRG = DSL.Make[PRG] 
18  
19     val authenticDSL: Free[PRG.Cop, Boolean] = 
20       for { 
21         uid <- Ask("Enter your user id:").freek[PRG] 
22         pwd <- Ask("Enter password:").freek[PRG] 
23         auth <- Authenticate(uid,pwd).freek[PRG] 
24       } yield auth 
25   }

从ADT到DSL设计,用freeK使代码简单了很多。我们不需要再对ADT进行Inject和Free.liftF升格了,但必须在没条语句后附加.freek[PRG]。本来可以通过隐式转换来避免这样的重复代码,但scalac会在编译时产生一些怪异现象。这个PRG就是freeK的Coproduct结构管理方法,PRG.Cop就是当前的Coproduct。freeK是用:|:符号来连接DSL的,替代了我们之前繁复的Inject操作。

功能实现方面有什么变化吗?

 1   object IMPLs { 
 2     import ADTs._ 
 3     import Interact._ 
 4     import Login._ 
 5     val idInteract = new (Interact ~> Id) { 
 6       def apply[A](ia: Interact[A]): Id[A] = ia match { 
 7         case Ask(p) => {println(p); scala.io.StdIn.readLine} 
 8         case Tell(m) => println(m) 
 9       } 
10     } 
11     val idLogin = new (Login ~> Id) { 
12       def apply[A](la: Login[A]): Id[A] = la match { 
13         case Authenticate(u,p) => (u,p) match { 
14           case ("Tiger","123") => true 
15           case _ => false 
16         } 
17       } 
18     } 
19     val interactLogin = idInteract :&: idLogin 
20   }

这部分没有什么变化。freeK用:&:符号替换了or操作符。

那我们又该如何运行用freeK编制的程序呢?

 

1 object freeKDemo extends App { 
2   import FreeKModules._ 
3   import DSLs._ 
4   import IMPLs._ 
5   val r0 = authenticDSL.foldMap(interactLogin.nat) 
6   val r = authenticDSL.interpret(interactLogin) 
7   println(r0) 
8   println(r) 
9 }

 interactLogin.nat就是以前的G[A]~>Id,所以我们依然可以用cats提供的foldMap来运算。不过freeK提供了更先进的interpret函数。它的特点是不要求Coproduct结构的构建顺序,我们无须再特别注意用inject构建Coproduct时的先后顺序了。也就是说:|:和:&:符号的左右元素可以不分,这将大大提高编程效率。

我们还是按上次的功能设计用Reader来进行用户密码验证功能的依赖注入。依赖界面定义如下:

1 object Dependencies { 
2   trait PasswordControl { 
3     val mapPasswords: Map[String,String] 
4     def matchUserPassword(uid: String, pwd: String): Boolean 
5   } 
6 }

我们需要把Interact和Login都对应到Reader:

 1     import Dependencies._ 
 2     type ReaderContext[A] = Reader[PasswordControl,A] 
 3     object readerInteract extends (Interact ~> ReaderContext) { 
 4       def apply[A](ia: Interact[A]): ReaderContext[A] = ia match { 
 5         case Ask(p) => Reader {pc => {println(p); scala.io.StdIn.readLine}} 
 6         case Tell(m) => Reader {_ => println(m)} 
 7       } 
 8     } 
 9     object readerLogin extends (Login ~> ReaderContext) { 
10       def apply[A](la: Login[A]): ReaderContext[A] = la match { 
11         case Authenticate(u,p) => Reader {pc => pc.matchUserPassword(u,p)} 
12       } 
13     } 
14     val userInteractLogin = readerLogin :&: readerInteract

注意在上面我故意调换了:&:符号两边对象来证明interpret函数是不依赖Coproduct顺序的。

运算时我们需要构建一个测试的PasswordControl实例,然后把它传入Reader.run函数:

 1 object freeKDemo extends App { 
 2   import FreeKModules._ 
 3   import DSLs._ 
 4   import IMPLs._ 
 5  // val r0 = authenticDSL.foldMap(interactLogin.nat) 
 6  // val r = authenticDSL.interpret(interactLogin) 
 7   import Dependencies._ 
 8   object UserPasswords extends PasswordControl { 
 9    override val mapPasswords: Map[String, String] = Map( 
10      "Tiger" -> "123", 
11      "John" -> "456" 
12    ) 
13    override def matchUserPassword(uid: String, pwd: String): Boolean = 
14      mapPasswords.getOrElse(uid,pwd+"!") == pwd 
15   } 
16  
17   interactLoginDSL.interpret(userInteractLogin).run(UserPasswords) 
18 }

 

测试运行正常。现在我们要尝试三个独立DSL的组合了。先增加一个用户权限验证DSL:

 

1     sealed trait Auth[+A] 
2     object Auth { 
3       case class Authorize(uid: String) extends Auth[Boolean] 
4     }

假如这个用户权限验证也是通过依赖注入的,我们先调整一下依赖界面:

 1 object Dependencies { 
 2   trait PasswordControl { 
 3     val mapPasswords: Map[String,String] 
 4     def matchUserPassword(uid: String, pswd: String): Boolean 
 5   } 
 6   trait AccessControl { 
 7     val mapAccesses: Map[String, Boolean] 
 8     def grandAccess(uid: String): Boolean 
 9   } 
10   trait Authenticator extends PasswordControl with AccessControl 
11 }

我们用Authenticator来代表包括PasswordControl,AccessControl的所有外部依赖。这样我们就需要把Reader的传入对象改变成Authenticator:

1     import Dependencies._ 
2     type ReaderContext[A] = Reader[Authenticator,A]

首先我们把增加的Auth语法与前两个语法构成的Coproduct再集合,然后进行集合三种语法的DSL编程:

 1   import Auth._ 
 2     type PRG3 = Auth :|: PRG   //Interact :|: Login :|: NilDSL 
 3     val PRG3 = DSL.Make[PRG3] 
 4     val authorizeDSL: Free[PRG3.Cop, Unit] = 
 5        for { 
 6          uid <- Ask("Enter your User ID:").freek[PRG3] 
 7          pwd <- Ask("Enter your Password:").freek[PRG3] 
 8          auth <- Authenticate(uid,pwd).freek[PRG3] 
 9          perm <-  if (auth) Authorize(uid).freek[PRG3] 
10                   else Free.pure[PRG3.Cop,Boolean](false) 
11           _ <- if (perm) Tell(s"Hello $uid, access granted!").freek[PRG3] 
12                else Tell(s"Sorry $uid, access denied!").freek[PRG3] 
13     } yield()

这个程序的功能具体实现方式如下:

1     val readerAuth = new (Auth ~> ReaderContext) { 
2       def apply[A](aa: Auth[A]): ReaderContext[A] = aa match { 
3         case Authorize(u) => Reader {ac => ac.grandAccess(u)} 
4       } 
5     } 
6     val userAuth = readerAuth :&: userInteractLogin

下面是测试数据制作以及运算:

 1   import Dependencies._ 
 2   object AuthControl extends Authenticator { 
 3     override val mapPasswords = Map( 
 4       "Tiger" -> "1234", 
 5       "John" -> "0000" 
 6     ) 
 7     override def matchUserPassword(uid: String, pswd: String) = 
 8       mapPasswords.getOrElse(uid, pswd+"!") == pswd 
 9  
10     override val mapAccesses = Map ( 
11       "Tiger" -> true, 
12       "John" -> false 
13     ) 
14     override def grandAccess(uid: String) = 
15       mapAccesses.getOrElse(uid, false) 
16   } 
17  
18 //  interactLoginDSL.interpret(userInteractLogin).run(AuthControl) 
19   authorizeDSL.interpret(userAuth).run(AuthControl)

测试运行结果:

 1 Enter your User ID: 
 2 Tiger 
 3 Enter your Password: 
 4 1234 
 5 Hello Tiger, access granted! 
 6  
 7 Process finished with exit code 0 
 8 ... 
 9 Enter your User ID: 
10 John 
11 Enter your Password: 
12 0000 
13 Sorry John, access denied! 
14  
15 Process finished with exit code 0

结果正是我们所预期的。在这次示范中我没费什么功夫就顺利的完成了一个三种语法DSL的编程示范。这说明freeK确实是个满意的Free编程工具。这次讨论的示范代码如下:

  1 import cats.free.Free 
  2 import cats.{Id, ~>} 
  3 import cats.data.Reader 
  4 import demo.app.FreeKModules.ADTs.Auth.Authorize 
  5 import freek._ 
  6 object FreeKModules { 
  7   object ADTs { 
  8     sealed trait Interact[+A] 
  9     object Interact { 
 10       case class Ask(prompt: String) extends Interact[String] 
 11       case class Tell(msg: String) extends Interact[Unit] 
 12     } 
 13     sealed trait Login[+A] 
 14     object Login { 
 15       case class Authenticate(uid: String, pwd: String) extends Login[Boolean] 
 16     } 
 17     sealed trait Auth[+A] 
 18     object Auth { 
 19       case class Authorize(uid: String) extends Auth[Boolean] 
 20     } 
 21   } 
 22   object DSLs { 
 23     import ADTs._ 
 24     import Interact._ 
 25     import Login._ 
 26     type PRG = Interact :|: Login :|: NilDSL 
 27     val PRG = DSL.Make[PRG] 
 28  
 29     val authenticDSL: Free[PRG.Cop, Boolean] = 
 30       for { 
 31         uid <- Ask("Enter your user id:").freek[PRG] 
 32         pwd <- Ask("Enter password:").freek[PRG] 
 33         auth <- Authenticate(uid,pwd).freek[PRG] 
 34       } yield auth 
 35  
 36     val interactLoginDSL: Free[PRG.Cop, Unit] = 
 37       for { 
 38         uid <- Ask("Enter your user id:").freek[PRG] 
 39         pwd <- Ask("Enter password:").freek[PRG] 
 40         auth <- Authenticate(uid,pwd).freek[PRG] 
 41         _ <- if (auth) Tell(s"Hello $uid, welcome to the zoo!").freek[PRG] 
 42              else Tell(s"Sorry, Who is $uid?").freek[PRG] 
 43       } yield () 
 44  
 45     import Auth._ 
 46     type PRG3 = Auth :|: PRG   //Interact :|: Login :|: NilDSL 
 47     val PRG3 = DSL.Make[PRG3] 
 48     val authorizeDSL: Free[PRG3.Cop, Unit] = 
 49        for { 
 50          uid <- Ask("Enter your User ID:").freek[PRG3] 
 51          pwd <- Ask("Enter your Password:").freek[PRG3] 
 52          auth <- Authenticate(uid,pwd).freek[PRG3] 
 53          perm <-  if (auth) Authorize(uid).freek[PRG3] 
 54                   else Free.pure[PRG3.Cop,Boolean](false) 
 55           _ <- if (perm) Tell(s"Hello $uid, access granted!").freek[PRG3] 
 56                else Tell(s"Sorry $uid, access denied!").freek[PRG3] 
 57     } yield() 
 58  
 59   } 
 60   object IMPLs { 
 61     import ADTs._ 
 62     import Interact._ 
 63     import Login._ 
 64     val idInteract = new (Interact ~> Id) { 
 65       def apply[A](ia: Interact[A]): Id[A] = ia match { 
 66         case Ask(p) => {println(p); scala.io.StdIn.readLine} 
 67         case Tell(m) => println(m) 
 68       } 
 69     } 
 70     val idLogin = new (Login ~> Id) { 
 71       def apply[A](la: Login[A]): Id[A] = la match { 
 72         case Authenticate(u,p) => (u,p) match { 
 73           case ("Tiger","123") => true 
 74           case _ => false 
 75         } 
 76       } 
 77     } 
 78     val interactLogin = idInteract :&: idLogin 
 79     import Dependencies._ 
 80     type ReaderContext[A] = Reader[Authenticator,A] 
 81     object readerInteract extends (Interact ~> ReaderContext) { 
 82       def apply[A](ia: Interact[A]): ReaderContext[A] = ia match { 
 83         case Ask(p) => Reader {pc => {println(p); scala.io.StdIn.readLine}} 
 84         case Tell(m) => Reader {_ => println(m)} 
 85       } 
 86     } 
 87     object readerLogin extends (Login ~> ReaderContext) { 
 88       def apply[A](la: Login[A]): ReaderContext[A] = la match { 
 89         case Authenticate(u,p) => Reader {pc => pc.matchUserPassword(u,p)} 
 90       } 
 91     } 
 92     val userInteractLogin = readerLogin :&: readerInteract 
 93  
 94     val readerAuth = new (Auth ~> ReaderContext) { 
 95       def apply[A](aa: Auth[A]): ReaderContext[A] = aa match { 
 96         case Authorize(u) => Reader {ac => ac.grandAccess(u)} 
 97       } 
 98     } 
 99     val userAuth = readerAuth :&: userInteractLogin 
100   } 
101  
102 } 
103 object Dependencies { 
104   trait PasswordControl { 
105     val mapPasswords: Map[String,String] 
106     def matchUserPassword(uid: String, pswd: String): Boolean 
107   } 
108   trait AccessControl { 
109     val mapAccesses: Map[String, Boolean] 
110     def grandAccess(uid: String): Boolean 
111   } 
112   trait Authenticator extends PasswordControl with AccessControl 
113 } 
114  
115 object freeKDemo extends App { 
116   import FreeKModules._ 
117   import DSLs._ 
118   import IMPLs._ 
119  // val r0 = authenticDSL.foldMap(interactLogin.nat) 
120  // val r = authenticDSL.interpret(interactLogin) 
121   import Dependencies._ 
122   object AuthControl extends Authenticator { 
123     override val mapPasswords = Map( 
124       "Tiger" -> "1234", 
125       "John" -> "0000" 
126     ) 
127     override def matchUserPassword(uid: String, pswd: String) = 
128       mapPasswords.getOrElse(uid, pswd+"!") == pswd 
129  
130     override val mapAccesses = Map ( 
131       "Tiger" -> true, 
132       "John" -> false 
133     ) 
134     override def grandAccess(uid: String) = 
135       mapAccesses.getOrElse(uid, false) 
136   } 
137  
138 //  interactLoginDSL.interpret(userInteractLogin).run(AuthControl) 
139   authorizeDSL.interpret(userAuth).run(AuthControl) 
140 }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

相关推荐

发表回复

登录后才能评论