在上一节我们讨论了通过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
}
功能实现方面有什么变化吗?
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","123") => true
case _ => false
}
}
}
val interactLogin = idInteract :&: idLogin
}
那我们又该如何运行用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)
}
我们还是按上次的功能设计用Reader来进行用户密码验证功能的依赖注入。依赖界面定义如下:
object Dependencies {
trait PasswordControl {
val mapPasswords: Map[String,String]
def matchUserPassword(uid: String, pwd: String): Boolean
}
}
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
运算时我们需要构建一个测试的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" -> "123",
"John" -> "456"
)
override def matchUserPassword(uid: String, pwd: String): Boolean =
mapPasswords.getOrElse(uid,pwd+"!") == pwd
}
interactLoginDSL.interpret(userInteractLogin).run(UserPasswords)
}
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
}
import Dependencies._
type ReaderContext[A] = Reader[Authenticator,A]
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" -> "1234",
"John" -> "0000"
)
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:
1234
Hello Tiger, access granted!
Process finished with exit code 0
...
Enter your User ID:
John
Enter your Password:
0000
Sorry John, access denied!
Process finished with exit code 0
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","123") => 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" -> "1234",
"John" -> "0000"
)
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)
}