在上一节我们讨论了通过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. 浅析MSIL中间语言——PE文件结构篇

    一.开篇 开篇我想讲一下于本文无关的话题,其实我很想美化一下自己博客园一直没时间弄,无意间找了博客园李宝亨的博客园里面有一篇分享自己主题的文章,我就将这个模板暂时用作我的blog主题,我要讲述一个关于 ...

  2. Generate input file for OVITO

    using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Tex ...

  3. Functional Programming without Lambda - Part 1 Functional Composition

    Functions in Java Prior to the introduction of Lambda Expressions feature in version 8, Java had lon ...

  4. TODO:小程序集成WeUI

    TODO:小程序集成WeUI WeUI 为微信 Web 服务量身设计.WeUI 是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信 Web 开发量身设计,可以令用户的使用感知更加统一. ...

  5. CSS移动端多行显示多余省略号

    /*css3 多行显示省略号,也可用于单行*/ .one-line { display: -webkit-box; overflow : hidden; text-overflow: ellipsis ...

  6. iOS中多线程知识总结(二)

    1.GCD GCD全称是Grand Central Dispatch,译为"强大的中枢管理器" 1)什么是任务?什么是队列? 任务和队列是GCD的核心. 任务: 执行什么操作 队列 ...

  7. react-native环境搭建

    目标平台 Android 开发平台 windows 开发环境安装建议:由于开发环境存在差异,建议参照react官网 或者react中文网 安装, react-native -- 在Windows下搭建 ...

  8. C#设计模式系列:模板方法模式(Template Method)

    你去银行取款的时候,银行会给你一张取款单,这张取款单就是一个模板,它把公共的内容提取到模板中,只留下部分让用户来填写.在软件系统中,将多个类的共有内容提取到一个模板中的思想便是模板方法模式的思想. 模 ...

  9. 【Win10应用开发】自定义打印选项

    老周在前一篇烂文中已经给大伙伴们演示了如何打印UI元素,今天的烂文就向各位介绍一下,如何向打印对话框添加自定义选项.如果只是讲如何实现,会比较抽象,也比较枯燥,而且相当无聊,更是说不清楚,毕竟这打印A ...

  10. Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用

    转载博客:http://blog.csdn.net/xiaanming/article/details/26810303 大家好!差不多两个来月没有写文章了,前段时间也是在忙换工作的事,准备笔试面试什 ...