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例子:
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的更多相关文章
- 学习java设计模式有用吗?懂这六个原则,编程更轻松
学习java设计模式有用吗?懂这六个原则,编程更轻松 1.开闭原则(Open Close Principle) 开闭原则就是说对扩展开放,对修改关闭.在程序需要进行拓展的时候,不能去修改原有的代码,实 ...
- 让编程更轻松的 7 个 Visual Studio 扩展 : 以下几个扩展,BuildVision可以用
是时候升级你最喜欢的IDE了!在这篇文章中,我将介绍一些我最喜欢的与众不同的 Visual Studio 扩展,是它们让我的日常编程工作变得更加轻松.对于一些明摆着的,例如 ReSharper 和 O ...
- 让网络编程更轻松和有趣 t-io
原文:https://www.oschina.net/p/t-io 注意:还是尽量去看原文,因为原文下面的评论也很有意思,可以参考大牛的讨论学习到新的东西 授权协议:Apache 开发语言:Java ...
- 人工智能都能写Java了!这款插件让你编程更轻松
最近在浏览技术社区,发现了一款 IDE 插件,利用人工智能技术帮助程序员高效写代码.节省开发时间,一下子勾起了我的好奇心. 下载之后,使用一番,确实蛮好的,可以有效提升编程效率. 这款插件叫:aixc ...
- 【转】【C#】C# 5.0 新特性——Async和Await使异步编程更简单
一.引言 在之前的C#基础知识系列文章中只介绍了从C#1.0到C#4.0中主要的特性,然而.NET 4.5 的推出,对于C#又有了新特性的增加--就是C#5.0中async和await两个关键字,这两 ...
- 10款让WEB前端开发人员更轻松的实用工具
这篇文章介绍10款让Web前端开发人员生活更轻松的实用工具.每个Web开发人员都有自己的工具箱,这样工作中碰到的每个问题都有一个好的解决方案供选择. 对于每一项工作,开发人员需要特定的辅助工具,所以如 ...
- 十款让 Web 前端开发人员更轻松的实用工具
这篇文章介绍十款让 Web 前端开发人员生活更轻松的实用工具.每个 Web 开发人员都有自己的工具箱,这样工作中碰到的每个问题都有一个好的解决方案供选择. 对于每一项工作,开发人员需要特定的辅助工具, ...
- Horseman - 让你更轻松的使用 PhantomJS
Horseman 是一个 Node.js 模块,让你可以更轻松的使用 PhantomJS 进行功能测试,页面自动机,网络监控,屏幕捕获等.它提供了直接,链式的 API,易于理解的控制流,避免回调陷阱. ...
- So Easy!让开发人员更轻松的工具和资源
这篇文章给大家分享让开发人员生活更轻松的免费工具和资源.所以,如果你正在寻找一些为迅速解决每天碰到的设计和开发问题的工具和资源,不要再观望,试试这些工具吧.这些奇妙的工具不仅会加快您的生产,也让你的工 ...
随机推荐
- 《Entity Framework 6 Recipes》中文翻译系列 (31) ------ 第六章 继承与建模高级应用之自引用关联
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-4 使用TPH建模自引用关系 问题 你有一张自引用的表,它代表数据库上不同类型 ...
- 【requireJS源码学习01】了解整个requireJS的结构
前言 现在工作中基本离不开requireJS这种模块管理工具了,之前一直在用,但是对其原理不甚熟悉,整两天我们来试着学习其源码,而后在探寻其背后的AMD思想吧 于是今天的目标是熟悉requireJS整 ...
- [译]AngularJS sercies - 获取后端数据
原文:ANGULARJS SERVICES – FETCHING SERVER DATA $http是AngularJS内置的服务,能帮助我们完成从服务端获数据.简单的用法就是在你需要数据的时候,发起 ...
- 【hadoop摸索系列】记录使用libhdfs访问hdfs的关键问题
hadoop官方的二进制发布版本一直是32位平台编译的,对于java来说跨平台不影响使用,但是为了在c/c++程序中操作hdfs就做不到了,因为libhdfs.so是二进制不兼容的. 我使用的是sta ...
- Sql Server系列:数据控制语句
数据控制语句用来设置.更改用户或角色的权限,包括GRANT.DENY.REVOKE等语句. GRANT用来对用户授予权限,REVOKE可用于删除已授权的权限,DENY用于防止主体通过GRANT获得特定 ...
- jQuery 2.0.3 源码分析Sizzle引擎 - 词法解析
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 浏览器从下载文档到显示页面的过程是个复杂的过程,这里包含了重绘和重排.各家浏览器引擎的工作原理略有差别,但也有一定规则. 简 ...
- 判断checkbox的checked状态(jQuery写法)
$('#checkboxInput').click(function(){ if (this.checked){ $('.questionContainer').css({ "opacity ...
- android模拟器默认位置的修改
1.创建ANDROID_SDK_HOME环境变量,如ANDROID_SDK_HOME=D:\eclipse_android\android-sdk 2.在ANDROID_SDK_HOME目录下,建立. ...
- google浏览器截图工具 Open Screenshot(代码截图)
有的时候特别想把自己写的代码保存为图片,但是代码的内容很长,用普通的截图工具只能一次一次的拼接起来,太麻烦了.这里使用了Google的截图插件,很好用. 或者使用360极速浏览器-->保存网页也 ...
- Neutron 功能概述 - 每天5分钟玩转 OpenStack(65)
从今天开始,我们将学习 OpenStack 的 Networking Service,Neutron.Neutron 的难度会比前面所有模块都大一些,内容也多一些.为了帮助大家更好的掌握 Neutor ...