zoukankan      html  css  js  c++  java
  • 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 }

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    ServiceStack.Redis之IRedisClient<第三篇>【转】
    Redis常用命令速查 <第二篇>【转】
    Redis 安装与简单示例 <第一篇>【转】
    OKHttp使用简介
    android studio快捷键
    android客户端向java服务端post发送json
    安卓开发--HttpDemo02
    安卓开发--HttpDemo01
    安卓开发--HttpClient
    安卓开发--AsyncTask2
  • 原文地址:https://www.cnblogs.com/tiger-xc/p/5855154.html
Copyright © 2011-2022 走看看