Scalaz(36)- Free :实践-Free In Action - 实用体验

在上面几期讨论中我们连续介绍了Free Monad。因为FP是纯函数编程,也既是纯函数的组合集成,要求把纯代码和副作用代码可以分离开来。Free Monad的程序描述(AST)和程序实现(Interpretation)关注分离(separation of concern)模式恰恰能满足FP要求。我们可以用一些代数数据类型(ADT Algebraic Data Type)来模拟功能,再把这些ADT组合起来形成AST(Abstract Syntax Tree)。AST既是对程序功能的描述,它的组成过程也就是Monadic Programming了。在另外一个过程中,我们可以按需要去实现各种Interpreter,从而达到实际运算的目的。我认为既然FP也被称为Monadic Programming,那么Free Monad应该是FP里最重要的数据结构,它的应用模式代表了主流FP,应该有个规范的具体使用方式。在本次讨论中我们将会集中对Free Monad的应用模式进行示范体验。

我们在这次示范中模拟一个针对键值存储(Key Value Store)的操作例子:

1、ADT设计

1 sealed trait KVS[+Next]
2 object KVS {
3  case class Get[Next](key: String, onValue: String => Next) extends KVS[Next]
4  case class Put[Next](key: String, value: String, n: Next) extends KVS[Next]
5  case class Del[Next](key: String, n: Next) extends KVS[Next]

KVS[+Next]就是一种F[A]类型。从Suspend[F[Free[F,A]]可以得出A类型即Free类型,那么Next就是一个Free类,代表Free的下一个状态。如果需要使用Next,F[_]必须是个Functor, 这样才能通过F.map(A=>B)来获取F[B],B==另一个Free。 Put,Del模拟了无返回结果指令,那么如果需要链接到下一个Free状态的话就直接把一个Free放人Next位置。Get返回一个String,onValue函数接过这个返回值再连接到下一个Free状态。

2、获取Functor实例

1   implicit val kvsFunctor = new Functor[KVS] {
2      def map[A,B](kvs: KVS[A])(f: A => B): KVS[B] = kvs match {
3        case Get(key, onResult) => Get(key, onResult andThen f)
4        case Put(key, value, next) => Put(key,value,f(next))
5        case Del(key,next) => Del(key,f(next))
6      }
7   }

把A转换成B就是把Free[KVS,A]转成Free[KVS,B],其实就是map over Next,对Next进行转换。对于函数C=>Next,map就是函数组合了:(C=>Next) andThen (Next=>B)。

3、类型升格,lift to Free

1 implicit def kvsToFree[A](ka: KVS[A]): Free[KVS,A] = Free.liftF(ka)
2  def put(key: String , value: String): Free[KVS,Unit] = Free.liftF(Put(key,value,()))
3  def get(key: String): Free[KVS,String] = Free.liftF(Get(key,identity))
4  def del(key: String): Free[KVS,Unit] = Free.liftF(Del(key,()))

包括隐式类型转换kvsToFree,可以把任何KVS[A]升格成Free[KVS,A]。独立指令升格put,get,del,因为不涉及下一个状态所以使用了()和identity。

4、Composition,Free Monad组合

1 import KVS._
2 def modify(key: String, f: String => String): Free[KVS,Unit] =
3   for {
4    v <- Get(key,identity)
5    _ <- Put(key,f(v), ())
6   } yield()              //> modify: (key: String, f: String => String)scalaz.Free[Exercises.freeExamples.KVS,Unit]

通过隐式函数kvsToFree把ADT Get,Put升格成Free[KVS,A],然后实现函数组合。

5、功能描述,AST设计

1 val script = for {
2   _ <- put("USA","United States Of America")
3   _ <- put("CHN","China")
4   _ <- put("PIL","Pilipines")
5   _ <- put("JPN","Japan")
6   _ <- modify("CHN",_ =>"People's Republic Of China")
7   _ <- del("PIL")
8   chn <- get("CHN")
9 } yield chn              //> script  : scalaz.Free[Exercises.freeExamples.KVS,String] = Gosub()

使用的是独立直接升格指令函数。函数直接返回了Free类型。就像是在for-loop里进行我们熟悉的行令编程:逐条指令编写。

6、功能实现,Interpretation

a、尾递归编译,tail-recursive interpretation

 1 def foldScript(kvs: Free[KVS,String],table: Map[String,String] = Map.empty): Map[String,String] =
 2   kvs.resume.fold (
 3   {
 4     case Get(key,onResult) => foldScript(onResult(table(key)), table)
 5     case Put(key,value, next) => foldScript(next, table + (key -> value))
 6     case Del(key,next) => foldScript(next, table - key)
 7   },
 8   _ => table
 9  )              //> foldScript: (kvs: scalaz.Free[Exercises.freeExamples.KVS,String], table: Map[String,String])Map[String,String]
10  foldScript(script,Map.empty)     //> res0: Map[String,String] = Map(USA -> United States Of America, CHN -> People's Republic Of China, JPN -> Japan)

注意,fold其实是Either.fold。foldScript是个尾递归函数。这时候Next就成为下一步递归的链接了。

b、foldMap,高阶类型转换,Natural Transformation,F[A]~>G[A]

1 type KVState[A] = State[Map[String,String],A]
2  object KvsToMap extends (KVS ~> KVState) {
3    def apply[A](kvs: KVS[A]): KVState[A] = kvs match {
4       case Get(key,onResult) => State { m => (m, onResult(m(key))) }
5       case Put(key,value,next) => State { m => (m + (key -> value), next) }
6       case Del(key,next) => State { m => (m - key, next) }
7    }
8  }
9  script.foldMap(KvsToMap).run(Map.empty)          //> res1: scalaz.Id.Id[(Map[String,String], String)] = (Map(USA -> United States Of America, CHN -> People's Republic Of China, JPN -> Japan),People's Republic Of China)

c、mutable实现方法:

 1 def goScript(kvs: Free[KVS,String],table: scala.collection.mutable.Map[String,String]):Unit =
 2    kvs.go {
 3      case Get(key,onResult) => onResult(table(key))
 4      case Put(key,value,next) => table += (key -> value); next
 5      case Del(key,next) => table -= key; next
 6    }                  //> goScript: (kvs: scalaz.Free[Exercises.freeExamples.KVS,String], table: scala.collection.mutable.Map[String,String])Unit
 7  val mutableMap = scala.collection.mutable.Map[String,String]()
 8                       //> mutableMap  : scala.collection.mutable.Map[String,String] = Map()
 9  goScript(script,mutableMap)
10  println(mutableMap)  //> Map(JPN -> Japan, CHN -> People's Republic Of China, USA -> United States Of America)

把完整的示范源代码提供给大家:

 1 package Exercises
 2 import scalaz._
 3 import Scalaz._
 4 import scala.language.higherKinds
 5 import scala.language.implicitConversions
 6 object freeExamples {
 7 sealed trait KVS[+Next]
 8 object KVS {
 9  case class Get[Next](key: String, onValue: String => Next) extends KVS[Next]
10  case class Put[Next](key: String, value: String, n: Next) extends KVS[Next]
11  case class Del[Next](key: String, n: Next) extends KVS[Next]
12   implicit val kvsFunctor = new Functor[KVS] {
13      def map[A,B](kvs: KVS[A])(f: A => B): KVS[B] = kvs match {
14        case Get(key, onResult) => Get(key, onResult andThen f)
15        case Put(key, value, next) => Put(key,value,f(next))
16        case Del(key,next) => Del(key,f(next))
17      }
18   }
19  implicit def kvsToFree[A](ka: KVS[A]): Free[KVS,A] = Free.liftF(ka)
20  def put(key: String , value: String): Free[KVS,Unit] = Free.liftF(Put(key,value,()))
21  def get(key: String): Free[KVS,String] = Free.liftF(Get(key,identity))
22  def del(key: String): Free[KVS,Unit] = Free.liftF(Del(key,()))
23 }
24 import KVS._
25 def modify(key: String, f: String => String): Free[KVS,Unit] =
26   for {
27    v <- Get(key,identity)
28    _ <- Put(key,f(v), ())
29   } yield()
30 val script = for {
31   _ <- put("USA","United States Of America")
32   _ <- put("CHN","China")
33   _ <- put("PIL","Pilipines")
34   _ <- put("JPN","Japan")
35   _ <- modify("CHN",_ =>"People's Republic Of China")
36   _ <- del("PIL")
37   chn <- get("CHN")
38 } yield chn
39 
40 def foldScript(kvs: Free[KVS,String],table: Map[String,String] = Map.empty): Map[String,String] =
41   kvs.resume.fold (
42   {
43     case Get(key,onResult) => foldScript(onResult(table(key)), table)
44     case Put(key,value, next) => foldScript(next, table + (key -> value))
45     case Del(key,next) => foldScript(next, table - key)
46   },
47   _ => table
48  )
49  foldScript(script,Map.empty)
50 
51  type KVState[A] = State[Map[String,String],A]
52  object KvsToMap extends (KVS ~> KVState) {
53    def apply[A](kvs: KVS[A]): KVState[A] = kvs match {
54       case Get(key,onResult) => State { m => (m, onResult(m(key))) }
55       case Put(key,value,next) => State { m => (m + (key -> value), next) }
56       case Del(key,next) => State { m => (m - key, next) }
57    }
58  }
59  script.foldMap(KvsToMap).run(Map.empty)
60  
61  def goScript(kvs: Free[KVS,String],table: scala.collection.mutable.Map[String,String]):Unit =
62    kvs.go {
63      case Get(key,onResult) => onResult(table(key))
64      case Put(key,value,next) => table += (key -> value); next
65      case Del(key,next) => table -= key; next
66    }
67  val mutableMap = scala.collection.mutable.Map[String,String]()
68  goScript(script,mutableMap)
69  println(mutableMap)
70 }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏CodingToDie

JPA @Query实现,动态代理,注解, 正则,Spring扩展的使用

上一篇文章中提到了如何使用注解完成一个简单的ORM,其中注解使用 JavaPersistenceAPI 但是其中没有我们需要的 @Query 和 @Param ...

411
来自专栏后端沉思录

自定义钉钉机器人报警

按照钉钉的文档来开发,创建机器人后,即可获取Webhook地址,整个过程还是很简单的,以上只是提供了一个思路.

1052
来自专栏杂烩

jedis针对三种redis工作模式的连接方式

BinaryJedisCluster.java是为了让jedis支持byte数组形式value重写的一个类,参考网上文档,记不得来源了。以下是代码(部分):

922
来自专栏CodingToDie

JPA @Query实现,动态代理,注解, 正则,Spring扩展的使用

@Query 的实现 动态代理 注解 表设计 model repository 大体流程 代理使用 将生成代理放入 Spring IOC 容器中 invoke方...

4076
来自专栏Java3y

Mybatis【关联映射】

Mybatis【多表连接】 我们在学习Hibernate的时候,如果表涉及到两张的话,那么我们是在映射文件中使用<set>..<many-to-one>等标签将...

2393
来自专栏小筱月

java ssm框架实现分页功能 (oracle)

LIMIT a,b : 参数 a:第 a 条数据开始查询(不包括第 a 条), 参数 b:查询 b 条数据

1202
来自专栏JAVA后端开发

解决SpringMVC使用fastJson后Long类型丢失精度的问题

2293
来自专栏码匠的流水账

聊聊spring cloud gateway的GatewayFilter

本文主要研究一下spring cloud gateway的GatewayFilter

1991
来自专栏Ryan Miao

在springmvc中配置jedis:

主要学习https://github.com/thinkgem/jeesite。一下代码均参考于此并稍作修改。 1.jedis 首先,需要添加jedis: <!...

3036
来自专栏游戏杂谈

使用ScriptableObject创建.asset文件

定义为public的变量就存储在xxx.asset中,通过 CommonConfig.Instance.Get 直接获取数据。

765

扫码关注云+社区