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!让开发人员更轻松的工具和资源
这篇文章给大家分享让开发人员生活更轻松的免费工具和资源.所以,如果你正在寻找一些为迅速解决每天碰到的设计和开发问题的工具和资源,不要再观望,试试这些工具吧.这些奇妙的工具不仅会加快您的生产,也让你的工 ...
随机推荐
- [译]Asp.net MVC 之 Contorllers(一)
Asp.net MVC contorllers 在Ajax全面开花的时代,ASP.NET Web Forms 开始慢慢变得落后.有人说,Ajax已经给了Asp.net致命一击.Ajax使越来越多的控制 ...
- ASP.NET Web API自身对CORS的支持: EnableCorsAttribute特性背后的故事
从编程的角度来讲,ASP.NET Web API针对CORS的实现仅仅涉及到HttpConfiguration的扩展方法EnableCors和EnableCorsAttribute特性.但是整个COR ...
- Android笔记——Android中visibility属性VISIBLE、INVISIBLE、GONE的区别
在Android开发中,大部分控件都有visibility这个属性,其属性有3个分别为"visible "."invisible"."gone&quo ...
- JavaScript 函数的两种声明方式
1.函数声明的方式 JavaScript声明函数有两种选择:函数声明法,表达式定义法. 函数声明法 function sum (num1 ,num2){ return num1+num2 } 表达式定 ...
- 【ssh信任关系】解决信任关系不生效问题
配置的时候遇见点问题,发现即便将id_rsa.pub拷贝到了另一台机器上,信任也没有建立起来. 原因是另外一台机器上目录权限不对,可以通过su root后观察/var/log/message里的日志信 ...
- python定时重跑获取数据
做大数据的童鞋经常会写定时任务跑数据,由于任务之间的依赖(一般都是下游依赖上游的数据产出),所以经常会导致数据获取失败,因为很多人发现数据失败后 都会去查看日志,然后手动去执行自己的任务.下面我实现了 ...
- SSISDB1:使用SSISDB管理SSIS Projects
使用Project Deployment Model,将SSIS Project部署到Integration Services Catalog之后,SSISDB负责管理SSIS Project.在SS ...
- html5的audio在safari(windows)中无效
因为mac下的safari不会有这样的问题(OSX默认都装的有QuickTime),而windows下用safari的比例实在小不用考虑. apple算是偷了一个小懒.而所谓的需要quicktime并 ...
- Mask裁切UI粒子特效或者3D模型
刚好前几天有人问我这个问题,再加上新项目也可能用,所以这两天就研究了一下.其实如果粒子特效 和3D模型 都用RenderTexture来做的话就不会有裁切的问题,但是粒子特效用RenderTextur ...
- Power BI官方视频(1) Power BI Desktop 7月份更新功能概述
2016年7月,Power BI Desktop进行了一些功能更新,提高整体的用户体验.同时也有一些新的和令人兴奋的功能.看看大概介绍,更新功能要点: 本文原文地址:Power BI官方视频(1) P ...