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 }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏影子

SpringMVC接收复杂对象

转载请注明地址:http://www.cnblogs.com/funnyzpc/p/7642977.html

1535
来自专栏京东技术

Sieve—Android 内存分析系统 | 解决你的内存溢出问题

内存问题是个老大难,对用户来说,泄漏或者不合理的内存使用最终会反映到性能和体验上,并且极易造成 OOM( Out Of Memories ) 而闪退, 而对开发...

1152
来自专栏影子

SpringMVC接收复杂对象

1044
来自专栏大数据钻研

Java设计模式之装饰模式趣谈

JVM:”上次给我招的工人不错啊!” oo程序员:”………..” JVM:”现在来我开的博物馆生意越来越好了,原来”舞台剧”的方式已经不能满足顾客的需求了”...

2627
来自专栏逍遥剑客的游戏开发

抛弃MFC, 转向.Net

2657
来自专栏aCloudDeveloper

python学习总结

最近经学长介绍学习python,为研究生做研究做准备,python对于科学计算有着很高的效率,对于科研人员当然是有着很强的诱惑,虽然我还没真正用它,但从整个学习...

1845
来自专栏北京马哥教育

两句话掌握 Python 最难知识点:元类

运维行业正在变革,推荐阅读:30万年薪Linux运维工程师成长魔法 千万不要被所谓“元类是99%的python程序员不会用到的特性”这类的说辞吓住。因为每个中国...

31311
来自专栏瞎说开发那些事

程序员应该避免的 5 种代码注释

1964
来自专栏葡萄城控件技术团队

Spread表格组件For JAVA功能介绍—表格相关操作

之前有篇文章我们说到 Spread 表格组件的 Java CTP 版本已经发布:《表格组件JAVACTP版本抢先预览》。 本篇我们来看一下他的主要功能,例如下...

1063
来自专栏HTML5学堂

JavaScript实现十进制转换成二进制

HTML5学堂:平时大家在写JavaScript效果的时候,可能会比较少接触二进制、八进制,经常使用的是十进制。计算机中的二进制则是一个非常微小的开关,用“开”...

2948

扫码关注云+社区