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","123") => 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" -> "123",
     "John" -> "456"
   )
   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" -> "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


结果正是我们所预期的。在这次示范中我没费什么功夫就顺利的完成了一个三种语法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","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)
}















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值