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

   object ADTs {
sealed trait Interact[+A]
object Interact {
case class Ask(prompt: String) extends Interact[String]
case class Tell(msg: String) extends Interact[Unit]
}
sealed trait Login[+A]
object Login {
case class Authenticate(uid: String, pwd: String) extends Login[Boolean]
}
}
object DSLs {
import ADTs._
import Interact._
import Login._
type PRG = Interact :|: Login :|: NilDSL
val PRG = DSL.Make[PRG] val authenticDSL: Free[PRG.Cop, Boolean] =
for {
uid <- Ask("Enter your user id:").freek[PRG]
pwd <- Ask("Enter password:").freek[PRG]
auth <- Authenticate(uid,pwd).freek[PRG]
} yield auth
}

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

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

   object IMPLs {
import ADTs._
import Interact._
import Login._
val idInteract = new (Interact ~> Id) {
def apply[A](ia: Interact[A]): Id[A] = ia match {
case Ask(p) => {println(p); scala.io.StdIn.readLine}
case Tell(m) => println(m)
}
}
val idLogin = new (Login ~> Id) {
def apply[A](la: Login[A]): Id[A] = la match {
case Authenticate(u,p) => (u,p) match {
case ("Tiger","") => true
case _ => false
}
}
}
val interactLogin = idInteract :&: idLogin
}

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

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

 object freeKDemo extends App {
import FreeKModules._
import DSLs._
import IMPLs._
val r0 = authenticDSL.foldMap(interactLogin.nat)
val r = authenticDSL.interpret(interactLogin)
println(r0)
println(r)
}

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

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

 object Dependencies {
trait PasswordControl {
val mapPasswords: Map[String,String]
def matchUserPassword(uid: String, pwd: String): Boolean
}
}

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

     import Dependencies._
type ReaderContext[A] = Reader[PasswordControl,A]
object readerInteract extends (Interact ~> ReaderContext) {
def apply[A](ia: Interact[A]): ReaderContext[A] = ia match {
case Ask(p) => Reader {pc => {println(p); scala.io.StdIn.readLine}}
case Tell(m) => Reader {_ => println(m)}
}
}
object readerLogin extends (Login ~> ReaderContext) {
def apply[A](la: Login[A]): ReaderContext[A] = la match {
case Authenticate(u,p) => Reader {pc => pc.matchUserPassword(u,p)}
}
}
val userInteractLogin = readerLogin :&: readerInteract

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

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

 object freeKDemo extends App {
import FreeKModules._
import DSLs._
import IMPLs._
// val r0 = authenticDSL.foldMap(interactLogin.nat)
// val r = authenticDSL.interpret(interactLogin)
import Dependencies._
object UserPasswords extends PasswordControl {
override val mapPasswords: Map[String, String] = Map(
"Tiger" -> "",
"John" -> ""
)
override def matchUserPassword(uid: String, pwd: String): Boolean =
mapPasswords.getOrElse(uid,pwd+"!") == pwd
} interactLoginDSL.interpret(userInteractLogin).run(UserPasswords)
}

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

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

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

 object Dependencies {
trait PasswordControl {
val mapPasswords: Map[String,String]
def matchUserPassword(uid: String, pswd: String): Boolean
}
trait AccessControl {
val mapAccesses: Map[String, Boolean]
def grandAccess(uid: String): Boolean
}
trait Authenticator extends PasswordControl with AccessControl
}

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

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

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

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

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

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

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

   import Dependencies._
object AuthControl extends Authenticator {
override val mapPasswords = Map(
"Tiger" -> "",
"John" -> ""
)
override def matchUserPassword(uid: String, pswd: String) =
mapPasswords.getOrElse(uid, pswd+"!") == pswd override val mapAccesses = Map (
"Tiger" -> true,
"John" -> false
)
override def grandAccess(uid: String) =
mapAccesses.getOrElse(uid, false)
} // interactLoginDSL.interpret(userInteractLogin).run(AuthControl)
authorizeDSL.interpret(userAuth).run(AuthControl)

测试运行结果:

 Enter your User ID:
Tiger
Enter your Password: Hello Tiger, access granted! Process finished with exit code
...
Enter your User ID:
John
Enter your Password: Sorry John, access denied! Process finished with exit code

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

 import cats.free.Free
import cats.{Id, ~>}
import cats.data.Reader
import demo.app.FreeKModules.ADTs.Auth.Authorize
import freek._
object FreeKModules {
object ADTs {
sealed trait Interact[+A]
object Interact {
case class Ask(prompt: String) extends Interact[String]
case class Tell(msg: String) extends Interact[Unit]
}
sealed trait Login[+A]
object Login {
case class Authenticate(uid: String, pwd: String) extends Login[Boolean]
}
sealed trait Auth[+A]
object Auth {
case class Authorize(uid: String) extends Auth[Boolean]
}
}
object DSLs {
import ADTs._
import Interact._
import Login._
type PRG = Interact :|: Login :|: NilDSL
val PRG = DSL.Make[PRG] val authenticDSL: Free[PRG.Cop, Boolean] =
for {
uid <- Ask("Enter your user id:").freek[PRG]
pwd <- Ask("Enter password:").freek[PRG]
auth <- Authenticate(uid,pwd).freek[PRG]
} yield auth val interactLoginDSL: Free[PRG.Cop, Unit] =
for {
uid <- Ask("Enter your user id:").freek[PRG]
pwd <- Ask("Enter password:").freek[PRG]
auth <- Authenticate(uid,pwd).freek[PRG]
_ <- if (auth) Tell(s"Hello $uid, welcome to the zoo!").freek[PRG]
else Tell(s"Sorry, Who is $uid?").freek[PRG]
} yield () import Auth._
type PRG3 = Auth :|: PRG //Interact :|: Login :|: NilDSL
val PRG3 = DSL.Make[PRG3]
val authorizeDSL: Free[PRG3.Cop, Unit] =
for {
uid <- Ask("Enter your User ID:").freek[PRG3]
pwd <- Ask("Enter your Password:").freek[PRG3]
auth <- Authenticate(uid,pwd).freek[PRG3]
perm <- if (auth) Authorize(uid).freek[PRG3]
else Free.pure[PRG3.Cop,Boolean](false)
_ <- if (perm) Tell(s"Hello $uid, access granted!").freek[PRG3]
else Tell(s"Sorry $uid, access denied!").freek[PRG3]
} yield() }
object IMPLs {
import ADTs._
import Interact._
import Login._
val idInteract = new (Interact ~> Id) {
def apply[A](ia: Interact[A]): Id[A] = ia match {
case Ask(p) => {println(p); scala.io.StdIn.readLine}
case Tell(m) => println(m)
}
}
val idLogin = new (Login ~> Id) {
def apply[A](la: Login[A]): Id[A] = la match {
case Authenticate(u,p) => (u,p) match {
case ("Tiger","") => true
case _ => false
}
}
}
val interactLogin = idInteract :&: idLogin
import Dependencies._
type ReaderContext[A] = Reader[Authenticator,A]
object readerInteract extends (Interact ~> ReaderContext) {
def apply[A](ia: Interact[A]): ReaderContext[A] = ia match {
case Ask(p) => Reader {pc => {println(p); scala.io.StdIn.readLine}}
case Tell(m) => Reader {_ => println(m)}
}
}
object readerLogin extends (Login ~> ReaderContext) {
def apply[A](la: Login[A]): ReaderContext[A] = la match {
case Authenticate(u,p) => Reader {pc => pc.matchUserPassword(u,p)}
}
}
val userInteractLogin = readerLogin :&: readerInteract val readerAuth = new (Auth ~> ReaderContext) {
def apply[A](aa: Auth[A]): ReaderContext[A] = aa match {
case Authorize(u) => Reader {ac => ac.grandAccess(u)}
}
}
val userAuth = readerAuth :&: userInteractLogin
} }
object Dependencies {
trait PasswordControl {
val mapPasswords: Map[String,String]
def matchUserPassword(uid: String, pswd: String): Boolean
}
trait AccessControl {
val mapAccesses: Map[String, Boolean]
def grandAccess(uid: String): Boolean
}
trait Authenticator extends PasswordControl with AccessControl
} object freeKDemo extends App {
import FreeKModules._
import DSLs._
import IMPLs._
// val r0 = authenticDSL.foldMap(interactLogin.nat)
// val r = authenticDSL.interpret(interactLogin)
import Dependencies._
object AuthControl extends Authenticator {
override val mapPasswords = Map(
"Tiger" -> "",
"John" -> ""
)
override def matchUserPassword(uid: String, pswd: String) =
mapPasswords.getOrElse(uid, pswd+"!") == pswd override val mapAccesses = Map (
"Tiger" -> true,
"John" -> false
)
override def grandAccess(uid: String) =
mapAccesses.getOrElse(uid, false)
} // interactLoginDSL.interpret(userInteractLogin).run(AuthControl)
authorizeDSL.interpret(userAuth).run(AuthControl)
}

Cats(3)- freeK-Free编程更轻松,Free programming with freeK的更多相关文章

  1. 学习java设计模式有用吗?懂这六个原则,编程更轻松

    学习java设计模式有用吗?懂这六个原则,编程更轻松 1.开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭.在程序需要进行拓展的时候,不能去修改原有的代码,实 ...

  2. 让编程更轻松的 7 个 Visual Studio 扩展 : 以下几个扩展,BuildVision可以用

    是时候升级你最喜欢的IDE了!在这篇文章中,我将介绍一些我最喜欢的与众不同的 Visual Studio 扩展,是它们让我的日常编程工作变得更加轻松.对于一些明摆着的,例如 ReSharper 和 O ...

  3. 让网络编程更轻松和有趣 t-io

    原文:https://www.oschina.net/p/t-io 注意:还是尽量去看原文,因为原文下面的评论也很有意思,可以参考大牛的讨论学习到新的东西 授权协议:Apache 开发语言:Java ...

  4. 人工智能都能写Java了!这款插件让你编程更轻松

    最近在浏览技术社区,发现了一款 IDE 插件,利用人工智能技术帮助程序员高效写代码.节省开发时间,一下子勾起了我的好奇心. 下载之后,使用一番,确实蛮好的,可以有效提升编程效率. 这款插件叫:aixc ...

  5. 【转】【C#】C# 5.0 新特性——Async和Await使异步编程更简单

    一.引言 在之前的C#基础知识系列文章中只介绍了从C#1.0到C#4.0中主要的特性,然而.NET 4.5 的推出,对于C#又有了新特性的增加--就是C#5.0中async和await两个关键字,这两 ...

  6. 10款让WEB前端开发人员更轻松的实用工具

    这篇文章介绍10款让Web前端开发人员生活更轻松的实用工具.每个Web开发人员都有自己的工具箱,这样工作中碰到的每个问题都有一个好的解决方案供选择. 对于每一项工作,开发人员需要特定的辅助工具,所以如 ...

  7. 十款让 Web 前端开发人员更轻松的实用工具

    这篇文章介绍十款让 Web 前端开发人员生活更轻松的实用工具.每个 Web 开发人员都有自己的工具箱,这样工作中碰到的每个问题都有一个好的解决方案供选择. 对于每一项工作,开发人员需要特定的辅助工具, ...

  8. Horseman - 让你更轻松的使用 PhantomJS

    Horseman 是一个 Node.js 模块,让你可以更轻松的使用 PhantomJS 进行功能测试,页面自动机,网络监控,屏幕捕获等.它提供了直接,链式的 API,易于理解的控制流,避免回调陷阱. ...

  9. So Easy!让开发人员更轻松的工具和资源

    这篇文章给大家分享让开发人员生活更轻松的免费工具和资源.所以,如果你正在寻找一些为迅速解决每天碰到的设计和开发问题的工具和资源,不要再观望,试试这些工具吧.这些奇妙的工具不仅会加快您的生产,也让你的工 ...

随机推荐

  1. 《FaceBook效应》——读后总结

    这本书讲述了facebook从如何创建.到风靡全球,并结合facebook的网络效应讲述为什么facebook可以做到社交龙头.读这本书的时候,也可以看看<社交网络>这部电影. faceb ...

  2. Oozie调度报错——ORA-00918:未明确定义列

    Oozie在执行sqoop的时候报错,同样的SQL在sqoop中可用,在oozie中不可用: Caused by: java.sql.SQLSyntaxErrorException: ORA-0091 ...

  3. iOS---后台运行机制详解

    一.iOS的“伪后台”程序 首先,先了解一下iOS 中所谓的「后台进程」到底是怎么回事吧? Let me be as clear as I can be: the iOS multitasking b ...

  4. rabbitMQ第五篇:Spring集成RabbitMQ

    前面几篇讲解了如何使用rabbitMq,这一篇主要讲解spring集成rabbitmq. 首先引入配置文件org.springframework.amqp,如下 <dependency> ...

  5. 传智播客--ADO.net--SqlBulkCopy批量插入数据(小白必知)

    一般情况下,我们在向数据库中插入数据时用Insert语句,但是当数据量很大的时候,这种情况就比较缓慢了,这个时候就需要SqlBulkCopy这个类. SqlBulkCopy本身常用的函数有这么几个 D ...

  6. C#中的readonly与const的比较

    C#中有两种常量类型,分别为readonly(运行时常量)与const(编译时常量),本文将就这两种类型的不同特性进行比较并说明各自的适用场景.工作原理 readonly为运行时常量,程序运行时进行赋 ...

  7. DOM-Document类型

    Document类型 JavaScript通过Document类型表示文档.在浏览器中,document对象是HTMLDocument(继承自Document类型)的一个实例,document对象是w ...

  8. Oracle_SQL函数-分组函数

    分组函数 什么是分组函数 分组函数作用于一组数据,并对一组数据返回一个值 组函数类型:主要有6种 AVG - 平均 COUNT - 计数 MAX - 最大 MIN - 最小 SUM - 求和 STDD ...

  9. React JS快速开始手册

    怎样用React JS构建一个用户界面?本文将快速地给你一个React JS的概览.代码,请君移步react-starter 概念 React只有很少的API,这使得它很容易去学习与理解.当然,使用它 ...

  10. Cesium应用篇:1快速搭建

    范例中所有范例可以在Github中搜索:ExamplesforCesium Cesium ['siːzɪəm]是一款开源的JavaScript开源库,开发者通过Cesium,实现无插件的创建三维球和二 ...