无须构建Coproduct
2、catsFreeLeftInjectInstance:Inject[F,Coproduct[F,G,?]]:构建Coproduct结构并将F放在左边
3、catsFreeRightInjectInstance:Inject[F,Coproduct[H,G,?]]:把F注入到已经包含H,G的Coproduct[H,G,?]
有了这三种实例后我们可以根据解析到的隐式实例类型使用inj函数通过Coproduct构建更大的语法集了。我们可以通过implicitly来验证一下Interact和Login语法的Inject隐式实例:
1 val selfInj = implicitly[Inject[Interact,Interact]] 2 type LeftInterLogin[A] = Coproduct[Interact,Login,A] 3 val leftInj = implicitly[Inject[Interact,LeftInterLogin]] 4 type RightInterLogin[A] = Coproduct[Login,LeftInterLogin,A] 5 val rightInj = implicitly[Inject[Interact,RightInterLogin]]
现在我们可以用Inject.inj和Free.liftF把Interact和Login升格成Free[G,A]。G是个类型变量,Interact和Login在Coproduct的最终左右位置由当前Inject隐式实例类型决定:
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 type FreeInteract[A] = Free[Interact,A] 7 //def ask(prompt: String): FreeInteract[String] = Free.liftF(Ask(prompt)) 8 //def tell(msg: String): FreeInteract[Unit] = Free.liftF(Tell(msg))
9 def ask[G[_]](prompt: String)(implicit I: Inject[Interact,G]): Free[G,String] =
10 Free.liftF(I.inj(Ask(prompt))) 11 def tell[G[_]](msg: String)(implicit I: Inject[Interact,G]): Free[G,Unit] =
12 Free.liftF(I.inj(Tell(msg))) 13 } 14
15 sealed trait Login[+A] 16 object Login { 17 type FreeLogin[A] = Free[Login,A] 18 case class Authenticate(user: String, pswd: String) extends Login[Boolean] 19 //def authenticate(user: String, pswd: String): FreeLogin[Boolean] = 20 // Free.liftF(Authenticate(user,pswd))
21 def authenticate[G[_]](user: String, pswd: String)(implicit I: Inject[Login,G]): Free[G,Boolean] =
22 Free.liftF(I.inj(Authenticate(user,pswd))) 23 }
现在我们可以用混合语法的DSL来编程了:
1 object DSLs { 2 import ADTs._ 3 import Interact._ 4 import Login._ 5 val interactDSL: FreeInteract[Unit] = for { 6 first <- ask("What's your first name?") 7 last <- ask("What's your last name?") 8 _ <- tell(s"Hello, $first $last!") 9 } yield() 10
11 val loginDSL: FreeLogin[Boolean] = for { 12 login <- authenticate("Tiger","123") 13 } yield login 14
15 type InteractLogin[A] = Coproduct[Interact,Login,A] 16 val interactLoginDSL: Free[InteractLogin,Boolean] = for { 17 uid <- ask[InteractLogin]("Enter your User ID:") 18 pwd <- ask[InteractLogin]("Enter your Password:") 19 aut <- authenticate[InteractLogin](uid,pwd) 20 } yield aut 21 }
在interactLoginDSL里所有ADT通过Inject隐式实例都被自动升格成统一的Free[Coproduct[Interact,Login,A]]。
interactLogin的功能实现方式之一示范如下:
1 object IMPLs { 2 import cats.{Id,~>} 3 import ADTs._,Interact._,Login._ 4 import DSLs._ 5 object InteractConsole extends (Interact ~> Id) { 6 def apply[A](ia: Interact[A]): Id[A] = ia match { 7 case Ask(p) => {println(p); readLine} 8 case Tell(m) => println(m) 9 } 10 } 11 object LoginMock extends (Login ~> Id) { 12 def apply[A](la: Login[A]): Id[A] = la match { 13 case Authenticate(u,p) => if (u == "Tiger" && p == "123") true else false
14 } 15 } 16 val interactLoginMock: (InteractLogin ~> Id) = InteractConsole.or(LoginMock) 17 }
这个interactLoginMock就是一个Interact,Login混合语法程序的功能实现。不过我们还是应该赋予Login一个比较实在点的实现:我们可以用一种依赖注入方式通过Reader数据类型把外部系统的用户密码验证的方法传入: