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 }
时间: 2024-10-24 10:26:10

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

老司机:如何让运维操作更轻松、高效

讲师介绍 庞辉富 广通软件技术总监 拥有10多年IT运维管理软件研发经验 致力于自动化运维解决方案的研究和推广 主导研发的产品广泛应用于海关.公安.能源等多个行业 技术发展给运维带来的挑战 当前的IT建设在这些新技术的演进下,我们看到的是呈现"双态IT"特征.Gartner也提出双模IT理论,与现在谈的双态IT是异曲同工的,不再是一种单纯的形态,而是两种形态交集在一起. 一种是稳态,也是我们经常说的核心业务,比如银行的核心业务.政府的核心业务等,业务系统一般以传统IOE或VCE架构设计

强大的vim配置文件,让编程更随意

花了很长时间整理的,感觉用起来很方便,共享一下. 我的vim配置主要有以下优点: 1.按F5可以直接编译并执行C.C++.java代码以及执行shell脚本,按"F8"可进行C.C++代码的调试 2.自动插入文件头 ,新建C.C++源文件时自动插入表头:包括文件名.作者.联系方式.建立时间等,读者可根据需求自行更改 3.映射"Ctrl + A"为全选并复制快捷键,方便复制代码 4.按"F2"可以直接消除代码中的空行 5."F3"

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

一.引言 在之前的C#基础知识系列文章中只介绍了从C#1.0到C#4.0中主要的特性,然而.NET 4.5 的推出,对于C#又有了新特性的增加--就是C#5.0中async和await两个关键字,这两个关键字简化了异步编程,之所以简化了,还是因为编译器给我们做了更多的工作,下面就具体看看编译器到底在背后帮我们做了哪些复杂的工作的. 二.同步代码存在的问题 对于同步的代码,大家肯定都不陌生,因为我们平常写的代码大部分都是同步的,然而同步代码却存在一个很严重的问题,例如我们向一个Web服务器发出一个

【转】十款让 Web 前端开发人员更轻松的实用工具

这篇文章介绍十款让 Web 前端开发人员生活更轻松的实用工具.每个 Web 开发人员都有自己的工具箱,这样工作中碰到的每个问题都有一个好的解决方案供选择. 对于每一项工作,开发人员需要特定的辅助工具,所以如果下面这些工具对于你来说都是新的领域,那么这篇文章是非常有用的,因为这些实用的工具将让你的工作更有效率. Spritepad 借助 SpritePad,你可以在几分钟甚至几秒钟内创建你的 CSS 精灵.只需拖放您的图片,立即可以生成 PNG 精灵图片以及 CSS 代码.不需要在 Photosh

Payssion,海外本地支付_海外本地收款_小语种本地支付_外贸收款_外贸网店收款_欧洲本地支付_俄罗斯本地支付_巴西支付_跨境支付_PAYSSION,让跨境支付更轻松!

Payssion,海外本地支付_海外本地收款_小语种本地支付_外贸收款_外贸网店收款_欧洲本地支付_俄罗斯本地支付_巴西支付_跨境支付_PAYSSION,让跨境支付更轻松! 首页 / 关于我们 / 关于Payssion 关于Payssion Payssion,让跨境支付更轻松! Payssion,专注于为中小企业和个人提供"安全.轻松.便捷"的全球在线支付解决方案,为客户提供全球在线收款服务.我们的团队成员来自支付宝.Paypal等支付公司,我们对全球支付方面有着长期的经验积累! 为什

人生维艰,何不利用开源.NET函数库让工作更轻松

今天推荐的文章会谈到一些让你工作更轻松的开源.NET函数库. 即使业界有时候认为.NET开源社区不太健康,很多开发团队都更多依赖于微软提供的东西来开发.不过最近在.NET世界中还是诞生了一些优秀和有意思的开源函数库. thomasvm就在他的博文中推荐了一些比较有代表性的开源函数库.这些函数库都是解决一些比较通用的问题,具备良好的文档,并非是一个强制你遵循某种规则和代码结构的框架,可以很好地嵌入到你的应用程序当中.当然,作者也给出了一些推荐理由: Hangfire.这是一个运行在ASP.NET中

VR拔牙更轻松?新的拔牙手段告别疼痛

原文标题:VR拔牙更轻松?新的拔牙手段告别疼痛 拔牙更轻松?能读取X光的 VR 头显 AR编辑器认为,我们大多数人都曾经经历过拔牙,在拔牙的时候由于我们的嘴巴只能张开到一定角度,医生在操作的时候需要转换各种角度再配以良好的光线才能顺利进行手术.现在正在进行的一个项目可以大大减轻负担. 在日本东京,一名医生正在演示用于牙科手术的 VR 模拟项目,用的是能够三维显示患者的X光片和其它详细资料的虚拟现实头盔.据外媒称,该项目是由 J·莫里塔公司(J.Morita Corp.)和"实现移动通信"

强大的vim配置文件,让编程更随意(转)

欢迎来到小码哥的博客 博客搬家啦 blog.ma6174.com 强大的vim配置文件,让编程更随意 花了很长时间整理的,感觉用起来很方便,共享一下. 我的vim配置主要有以下优点: 1.按F5可以直接编译并执行C.C++.java代码以及执行shell脚本,按"F8"可进行C.C++代码的调试 2.自动插入文件头 ,新建C.C++源文件时自动插入表头:包括文件名.作者.联系方式.建立时间等,读者可根据需求自行更改 3.映射"Ctrl + A"为全选并复制快捷键,方

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

这篇文章介绍10款让Web前端开发人员生活更轻松的实用工具.每个Web开发人员都有自己的工具箱,这样工作中碰到的每个问题都有一个好的解决方案供选择. 对于每一项工作,开发人员需要特定的辅助工具,所以如果下面这些工具对于你来说都是新的领域,那么这篇文章是非常有用的,因为这些实用的工具将让你的工作更有效率. Spritepad 借助 SpritePad,你可以在几分钟甚至几秒钟内创建你的CSS Sprite.只需拖放您的图片,立即可以生成 PNG 精灵图片以及CSS代码.不需要在 Photoshop