前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Cats(3)- freeK-Free编程更轻松,Free programming with freeK

Cats(3)- freeK-Free编程更轻松,Free programming with freeK

作者头像
用户1150956
发布2018-01-05 10:56:37
6590
发布2018-01-05 10:56:37
举报

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

代码语言:javascript
复制
 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操作。

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

代码语言:javascript
复制
 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编制的程序呢?

代码语言:javascript
复制
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来进行用户密码验证功能的依赖注入。依赖界面定义如下:

代码语言:javascript
复制
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:

代码语言:javascript
复制
 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函数:

代码语言:javascript
复制
 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:

代码语言:javascript
复制
1     sealed trait Auth[+A]
2     object Auth {
3       case class Authorize(uid: String) extends Auth[Boolean]
4     }

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

代码语言:javascript
复制
 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:

代码语言:javascript
复制
1     import Dependencies._
2     type ReaderContext[A] = Reader[Authenticator,A]

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

代码语言:javascript
复制
 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()

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

代码语言:javascript
复制
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

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

代码语言:javascript
复制
 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)

测试运行结果:

代码语言:javascript
复制
 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编程工具。这次讨论的示范代码如下:

代码语言:javascript
复制
  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 }
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-09-09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档