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 条评论
登录 后参与评论

相关文章

来自专栏芋道源码1024

告诉你 Redis 是一个牛逼货

Redis 是一个 Key-Value 存储系统。和 Memcached 类似,它支持存储的 value 类型相对更多,包括 string(字符串)、 list...

860
来自专栏令仔很忙

机房收费系统之VB报表

简单地说:报表就是用图表、表格等格式来动态显示数据,用一个公式来表示的话,就是“报表 = 多样的格式 + 动态的数据”,报表可以说是企业管理的基本措施和途...

692
来自专栏about云

spark streaming知识总结[优化]

问题导读 1.DStreams的含义是什么? 2.DStreams提供哪两种类型的操作? 3.Transformations操作分为哪两种类型? 4.本文说...

4394
来自专栏携程技术中心

高性能Key/Value存储引擎SessionDB

简介 随着公司业务量的逐年成长,粘性会话(Sticky Session)越来越成为应用横向扩展(Scale Out)的瓶颈,为消除粘性会话,支持应用无状态(St...

21310
来自专栏芋道源码1024

分布式作业 Elastic-Job-Lite 源码分析 —— 作业配置

一个作业( ElasticJob )的调度,需要配置独有的一个作业调度器( JobScheduler ),两者是 1 : 1 的关系。这点大家要注意下,当然下文...

905
来自专栏CSDN技术头条

使用Mongo Connector和Elasticsearch实现模糊匹配

【编者按】本篇博文作者Luke Lovett是MongoDB公司的Java工程师,他展示了Mongo Connector经过2年发展后的蜕变——完成连接器两端的...

2035
来自专栏重庆的技术分享区

PySpark简介

Apache Spark是一个大数据处理引擎,与MapReduce相比具有多个优势。通过删除Hadoop中的大部分样板代码,Spark提供了更大的简单性。此外,...

1002
来自专栏青枫的专栏

FIFO、UART、ALE解释

FIFO存储器 FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器。

863
来自专栏Spark学习技巧

spark源码系列之内部通讯的三种机制

本文是以spark1.6.0的源码为例讲解。 Spark为协调各个组件完成任务及内部任务处理采用了多种方式进行了各个组件之间的通讯。总共三个部分牵涉的功能是: ...

2398
来自专栏IT笔记

JavaWeb项目架构之Redis分布式日志队列

架构、分布式、日志队列,标题自己都看着唬人,其实就是一个日志收集的功能,只不过中间加了一个Redis做消息队列罢了。 ? 前言 为什么需要消息队列? 当系统中出...

34013

扫码关注云+社区