Cats(1)- 从Free开始,Free cats
来源:互联网 发布:淘宝免费开店步骤 编辑:程序博客网 时间:2024/06/08 02:37
cats是scala的一个新的函数式编程工具库,其设计原理基本继承了scalaz:大家都是haskell typeclass的scala版实现。当然,cats在scalaz的基础上从实现细节、库组织结构和调用方式上进行了一些优化,所以对用户来说:cats的基础数据类型、数据结构在功能上与scalaz是大致相同的,可能有一些语法上的变化。与scalaz著名抽象、复杂的语法表现形式相比,cats的语法可能更形象、简单直白。在scalaz的学习过程中,我们了解到所谓函数式编程就是monadic Programming:即用monad这样的数据类型来构建程序。而实际可行的monadic programming就是用Free-Monad编程了。因为Free-Monad程序是真正可运行的,或者说是可以实现安全运行的,因为它可以保证在固定的堆栈内实现无限运算。我们知道:函数式编程模式的运行方式以递归算法为主,flatMap函数本身就是一种递归算法。这就预示着monadic programming很容易造成堆栈溢出问题(StackOverflowError)。当我们把普通的泛函类型F[A]升格成Free-Monad后就能充分利用Free-Monad安全运算能力来构建实际可运行的程序了。由于我们在前面已经详细的了解了scalaz的大部分typeclass,包括Free,对cats的讨论就从Free开始,聚焦在cats.Free编程模式方面。同时,我们可以在使用cats.Free的过程中对cats的其它数据类型进行补充了解。
cats.Free的类型款式如下:
sealed abstract class Free[S[_], A] extends Product with Serializable {...}
S是个高阶类,就是一种函数式运算。值得注意的是:现在S不需要是个Functor了。因为Free的一个实例Suspend类型是这样的:
/** Suspend the computation with the given suspension. */ private final case class Suspend[S[_], A](a: S[A]) extends Free[S, A]
/** * Suspend a value within a functor lifting it to a Free. */ def liftF[F[_], A](value: F[A]): Free[F, A] = Suspend(value)
Free程序的特点是算式(description)/算法(implementation)关注分离(separation of concern):我们用一组数据类型来模拟一种编程语句ADT(algebraic data type),这一组ADT就形成了一种定制的编程语言DSL(domain specific language)。Free的编程部分就是用DSL来描述程序功能(description of purpose),即算式了。算法即用DSL描述的功能的具体实现,可以有多种的功能实现方式。我们先看个简单的DSL:
import cats.free._import cats.Functorobject catsFree { object ADTs { sealed trait Interact[+A] object Interact { case class Ask(prompt: String) extends Interact[String] case class Tell(msg: String) extends Interact[Unit] def ask(prompt: String): Free[Interact,String] = Free.liftF(Ask(prompt)) def tell(msg: String): Free[Interact,Unit] = Free.liftF(Tell(msg)) implicit object interactFunctor extends Functor[Interact] { def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ??? /* ia match { case Ask(p) => ??? case Tell(m) => ??? } */ } } } object DSLs { import ADTs._ import Interact._ val prg: Free[Interact,Unit] = for { first <- ask("What's your first name?") last <- ask("What's your last name?") _ <- tell(s"Hello $first $last") } yield() }
在这个例子里Interact并不是一个Functor,因为我们无法获取Interact Functor实例的map函数。先让我们分析一下Functor的map:
implicit object interactFunctor extends Functor[Interact] { def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ia match { case Ask(p) => ??? case Tell(m) => ??? } }
map的作用是用一个函数A => B把F[A]转成F[B]。也就是把语句状态从F[A]转成F[B],但在Interact的情况里F[B]已经是明确的Interact[Unit]和Interact[String]两种状态,而map的f是A => B,在上面的示范里我们该如何施用f来获取这个Interact[B]呢?从上面的示范里我们观察可以得出Ask和Tell这两个ADT纯粹是为了模拟ask和tell这两个函数。ask和tell分别返回Free版本的String,Unit结果。可以说:Interact并没有转换到下一个状态的要求。那么假如我们把ADT调整成下面这样呢:
sealed trait FunInteract[NS] object FunInteract { case class FunAsk[NS](prompt: String, onInput: String => NS) extends FunInteract[NS] case class FunTell[NS](msg: String, ns: NS) extends FunInteract[NS] def funAsk(prompt: String): Free[FunInteract,String] = Free.liftF(FunAsk(prompt,identity)) def funAskInt(prompt: String): Free[FunInteract,Int] = Free.liftF(FunAsk(prompt,_.toInt)) def funTell(msg: String): Free[FunInteract,Unit] = Free.liftF(FunTell(msg,())) implicit object funInteract extends Functor[FunInteract] { def map[A,NS](fa: FunInteract[A])(f: A => NS) = fa match { case FunAsk(prompt,input) => FunAsk(prompt,input andThen f) case FunTell(msg,ns) => FunTell(msg,f(ns)) } } }
现在这两个ADT是有类型参数NS的了:FunAsk[NS],FunTell[NS]。NS代表了ADT当前类型,如FunAsk[Int]、FunTell[String]...,现在这两个ADT都通过类型参数NS变成了可map的对象了,如FunAsk[String] >>> FunAsk[String], FunAsk[String] >>> FunAsk[Int]...。所以我们可以很顺利的实现object funInteract的map函数。但是,一个有趣的现象是:为了实现这种状态转换,如果ADT需要返回操作结果,就必须具备一个引领状态转换的机制,如FunAsk类型里的onInput: String => NS:它代表funAsk函数返回的结果可以指向下一个状态。新增函数funAskInt是个很好的示范:通过返回的String结果将状态转换到FunAsk[Int]状态。函数funTell不返回结果,所以FunTell没有状态转换机制。scalaz旧版本Free.Suspend的类型款式是:Suspend[F[Free,A]],这是一个递归类型,内部的Free代表下一个状态。由于我们必须用F.map才能取出下一个状态,所以F必须是个Functor。我们应该注意到如果ADT是Functor的话会造成Free程序的冗余代码。既然cats.Free对F[A]没有设置Functor门槛,那么我们应该尽量避免使用Functor。
得出对ADT类型要求结论后,我们接着示范cats的Free编程。下面是Free程序的功能实现interpret部分(implementation):
import ADTs._ object iconsole extends (Interact ~> Id) { def apply[A](ia: Interact[A]): Id[A] = ia match { case Ask(p) => {println(p); readLine} case Tell(m) => println(m) } } }
DSL程序的功能实现就是把ADT F[A]对应到实际的指令集G[A],在Free编程里用NaturalTransformation ~>来实现。注意G[A]必须是个Monad。在上面的例子里对应关系是:Interact~>Id,代表直接对应到运算指令println和readLine。我们也可以实现另一个版本:
type Prompt = String type Reply = String type Message = String type Tester[A] = Map[Prompt,Reply] => (List[Message],A) object tester extends (Interact ~> Tester) { def apply[A](ia: Interact[A]): Tester[A] = ia match { case Ask(p) => { m => (List(), m(p)) } case Tell(m) => { _ => (List(m), ()) } } } import cats.Monad implicit val testerMonad = new Monad[Tester] { override def pure[A](a: A): Tester[A] = _ => (List(),a) override def flatMap[A,B](ta: Tester[A])(f: A => Tester[B]): Tester[B] = m => { val (o1,a1) = ta(m) val (o2,a2) = f(a1)(m) (o1 ++ o2, a2) } override def tailRecM[A,B](a: A)(f: A => Tester[Either[A,B]]): Tester[B] = defaultTailRecM(a)(f) } }
import cats.data.WriterT type WF[A] = Map[Prompt,Reply] => A type WriterTester[A] = WriterT[WF,List[Message],A] def testerToWriter[A](f: Map[Prompt,Reply] => (List[Message],A)) = WriterT[WF,List[Message],A](f) object testWriter extends (Interact ~> WriterTester) { import Interact._ def apply[A](ia: Interact[A]): WriterTester[A] = ia match { case Ask(p) => testerToWriter(m => (List(),m(p))) case Tell(m) => testerToWriter(_ => (List(m),())) } }
如果我们用Writer来实现Interact,实际上就是把Ask和Tell都升格成Writer类型。
我们再来看看在cats里是如何运算Free DSL程序的。相对scalaz而言,cats的运算函数简单的多,就一个foldMap,我们来看看它的定义:
/** * Catamorphism for `Free`. * * Run to completion, mapping the suspension with the given * transformation at each step and accumulating into the monad `M`. * * This method uses `tailRecM` to provide stack-safety. */ final def foldMap[M[_]](f: FunctionK[S, M])(implicit M: Monad[M], r: RecursiveTailRecM[M]): M[A] = r.sameType(M).tailRecM(this)(_.step match { case Pure(a) => M.pure(Right(a)) case Suspend(sa) => M.map(f(sa))(Right(_)) case FlatMapped(c, g) => M.map(c.foldMap(f))(cc => Left(g(cc))) })
/** * This is a marker type that promises that the method * .tailRecM for this type is stack-safe for arbitrary recursion. */trait RecursiveTailRecM[F[_]] extends Serializable { /* * you can call RecursiveTailRecM[F].sameType(Monad[F]).tailRec * to have a static check that the types agree * for safer usage of tailRecM */ final def sameType[M[_[_]]](m: M[F]): M[F] = m}
/** * Same as foldMap but without a guarantee of stack safety. If the recursion is shallow * enough, this will work */ final def foldMapUnsafe[M[_]](f: FunctionK[S, M])(implicit M: Monad[M]): M[A] = foldMap[M](f)(M, RecursiveTailRecM.create)
import cats.Monad implicit val testerMonad = new Monad[Tester] with RecursiveTailRecM[Tester]{ override def pure[A](a: A): Tester[A] = _ => (List(),a) override def flatMap[A,B](ta: Tester[A])(f: A => Tester[B]): Tester[B] = m => { val (o1,a1) = ta(m) val (o2,a2) = f(a1)(m) (o1 ++ o2, a2) } override def tailRecM[A,B](a: A)(f: A => Tester[Either[A,B]]): Tester[B] = defaultTailRecM(a)(f) }
然后我们制造一些测试数据:
val testData = Map("What's your first name?" -> "Tiger", "What's your last name?" -> "Chan") //> testData : scala.collection.immutable.Map[String,String] = Map(What's your first name? -> Tiger, What's your last name? -> Chan)
import ADTs._,DSLs._,IMPLs._ val testData = Map("What's your first name?" -> "Tiger", "What's your last name?" -> "Chan") //> testData : scala.collection.immutable.Map[String,String] = Map(What's your first name? -> Tiger, What's your last name? -> Chan) val prgRunner = prg.foldMap(tester) //> prgRunner : demo.ws.catsFree.IMPLs.Tester[Unit] = <function1> prgRunner(testData) //> res0: (List[demo.ws.catsFree.IMPLs.Message], Unit) = (List(Hello Tiger Chan),())
那么如果运算testWriter呢?我们先取得WriterT的Monad实例:
implicit val testWriterMonad = WriterT.catsDataMonadWriterForWriterT[WF,List[Message]]
implicit val testWriterRecT = new RecursiveTailRecM[WriterTester]{} //> testWriterRecT : cats.RecursiveTailRecM[demo.ws.catsFree.IMPLs.WriterTester] = demo.ws.catsFree$$anonfun$main$1$$anon$2@6093dd95 val prgRunner = prg.foldMap(testWriter) //> prgRunner : demo.ws.catsFree.IMPLs.WriterTester[Unit] = WriterT(<function1>) prgRunner.run(testData)._1.map(println) //> Hello Tiger Chan //| res0: List[Unit] = List(())
我们再示范一下cats官方文件里关于free monad例子:模拟一个KVStore的put,get,delete功能。ADT设计如下:
object ADTs { sealed trait KVStoreA[+A] case class Put[T](key: String, value: T) extends KVStoreA[Unit] case class Get[T](key: String) extends KVStoreA[Option[T]] case class Del(key: String) extends KVStoreA[Unit] }
type KVStore[A] = Free[KVStoreA,A] object KVStoreA { def put[T](key: String, value: T): KVStore[Unit] = Free.liftF[KVStoreA,Unit](Put[T](key,value)) def get[T](key: String): KVStore[Option[T]] = Free.liftF[KVStoreA,Option[T]](Get[T](key)) def del(key: String): KVStore[Unit] = Free.liftF[KVStoreA,Unit](Del(key)) def mod[T](key: String, f: T => T): KVStore[Unit] = for { opt <- get[T](key) _ <- opt.map {t => put[T](key,f(t))}.getOrElse(Free.pure(())) } yield() }
注意一下mod函数:它是由基础函数get和put组合而成。我们要求所有在for内的类型为Free[KVStoreA,?],所以当f函数施用后如果opt变成None时就返回结果Free.pure(()),它的类型是:Free[Nothing,Unit],Nothing是KVStoreA的子类。
现在我们可以用这个DSL来编制KVS程序了:
object DSLs { import ADTs._ import KVStoreA._ def prg: KVStore[Option[Int]] = for { _ <- put[Int]("wild-cats", 2) _ <- mod[Int]("wild-cats", (_ + 12)) _ <- put[Int]("tame-cats", 5) n <- get[Int]("wild-cats") _ <- del("tame-cats") } yield n }
我们可以通过State数据结纯代码(pure code)方式来实现用immutable map的KVStore:
object IMPLs { import ADTs._ import cats.{~>} import cats.data.State type KVStoreState[A] = State[Map[String, Any], A] val kvsToState: KVStoreA ~> KVStoreState = new (KVStoreA ~> KVStoreState) { def apply[A](fa: KVStoreA[A]): KVStoreState[A] = fa match { case Put(key, value) => State { (s:Map[String, Any]) => (s.updated(key, value),()) } case Get(key) => State { (s:Map[String, Any]) => (s,s.get(key).asInstanceOf[A]) } case Del(key) => State { (s:Map[String, Any]) => (s - key, (())) } } } }
import ADTs._,DSLs._,IMPLs._ val prgRunner = prg.foldMap(kvsToState) //> prgRunner : demo.ws.catsFreeKVS.IMPLs.KVStoreState[Option[Int]] = cats.data.StateT@2cfb4a64 prgRunner.run(Map.empty).value //> res0: (Map[String,Any], Option[Int]) = (Map(wild-cats -> 14),Some(14))
import cats.{Monad,RecursiveTailRecM} implicitly[Monad[KVStoreState]] //> res1: cats.Monad[demo.ws.catsFreeKVS.IMPLs.KVStoreState] = cats.data.StateT Instances$$anon$2@71bbf57e implicitly[RecursiveTailRecM[KVStoreState]] //> res2: cats.RecursiveTailRecM[demo.ws.catsFreeKVS.IMPLs.KVStoreState] = cats.RecursiveTailRecM$$anon$1@7f13d6e
private[data] sealed trait StateTInstances2 { implicit def catsDataMonadForStateT[F[_], S](implicit F0: Monad[F]): Monad[StateT[F, S, ?]] = new StateTMonad[F, S] { implicit def F = F0 } implicit def catsDataRecursiveTailRecMForStateT[F[_]: RecursiveTailRecM, S]: RecursiveTailRecM[StateT[F, S, ?]] = RecursiveTailRecM.create[StateT[F, S, ?]] implicit def catsDataSemigroupKForStateT[F[_], S](implicit F0: Monad[F], G0: SemigroupK[F]): SemigroupK[StateT[F, S, ?]] = new StateTSemigroupK[F, S] { implicit def F = F0; implicit def G = G0 }}
Interact:
import cats.free._import cats.{Functor, RecursiveTailRecM}object catsFree { object ADTs { sealed trait Interact[+A] object Interact { case class Ask(prompt: String) extends Interact[String] case class Tell(msg: String) extends Interact[Unit] def ask(prompt: String): Free[Interact,String] = Free.liftF(Ask(prompt)) def tell(msg: String): Free[Interact,Unit] = Free.liftF(Tell(msg)) implicit object interactFunctor extends Functor[Interact] { def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ??? /* ia match { case Ask(p) => ??? case Tell(m) => ??? } */ } sealed trait FunInteract[NS] object FunInteract { case class FunAsk[NS](prompt: String, onInput: String => NS) extends FunInteract[NS] case class FunTell[NS](msg: String, ns: NS) extends FunInteract[NS] def funAsk(prompt: String): Free[FunInteract,String] = Free.liftF(FunAsk(prompt,identity)) def funAskInt(prompt: String): Free[FunInteract,Int] = Free.liftF(FunAsk(prompt,_.toInt)) def funTell(msg: String): Free[FunInteract,Unit] = Free.liftF(FunTell(msg,())) implicit object funInteract extends Functor[FunInteract] { def map[A,NS](fa: FunInteract[A])(f: A => NS) = fa match { case FunAsk(prompt,input) => FunAsk(prompt,input andThen f) case FunTell(msg,ns) => FunTell(msg,f(ns)) } } } } } object DSLs { import ADTs._ import Interact._ val prg: Free[Interact,Unit] = for { first <- ask("What's your first name?") last <- ask("What's your last name?") _ <- tell(s"Hello $first $last") } yield() } object IMPLs { import cats.{Id,~>} import ADTs._ import Interact._ object iconsole extends (Interact ~> Id) { def apply[A](ia: Interact[A]): Id[A] = ia match { case Ask(p) => {println(p); readLine} case Tell(m) => println(m) } } type Prompt = String type Reply = String type Message = String type Tester[A] = Map[Prompt,Reply] => (List[Message],A) object tester extends (Interact ~> Tester) { def apply[A](ia: Interact[A]): Tester[A] = ia match { case Ask(p) => { m => (List(), m(p)) } case Tell(m) => { _ => (List(m), ()) } } } import cats.Monad implicit val testerMonad = new Monad[Tester] with RecursiveTailRecM[Tester]{ override def pure[A](a: A): Tester[A] = _ => (List(),a) override def flatMap[A,B](ta: Tester[A])(f: A => Tester[B]): Tester[B] = m => { val (o1,a1) = ta(m) val (o2,a2) = f(a1)(m) (o1 ++ o2, a2) } override def tailRecM[A,B](a: A)(f: A => Tester[Either[A,B]]): Tester[B] = defaultTailRecM(a)(f) } import cats.data.WriterT import cats.instances.all._ type WF[A] = Map[Prompt,Reply] => A type WriterTester[A] = WriterT[WF,List[Message],A] def testerToWriter[A](f: Map[Prompt,Reply] => (List[Message],A)) = WriterT[WF,List[Message],A](f) implicit val testWriterMonad = WriterT.catsDataMonadWriterForWriterT[WF,List[Message]] object testWriter extends (Interact ~> WriterTester) { import Interact._ def apply[A](ia: Interact[A]): WriterTester[A] = ia match { case Ask(p) => testerToWriter(m => (List(),m(p))) case Tell(m) => testerToWriter(_ => (List(m),())) } } } import ADTs._,DSLs._,IMPLs._ val testData = Map("What's your first name?" -> "Tiger", "What's your last name?" -> "Chan") //val prgRunner = prg.foldMap(tester) //prgRunner(testData) implicit val testWriterRecT = new RecursiveTailRecM[WriterTester]{} val prgRunner = prg.foldMap(testWriter) prgRunner.run(testData)._1.map(println)}
import cats.free._import cats.instances.all._object catsFreeKVS { object ADTs { sealed trait KVStoreA[+A] case class Put[T](key: String, value: T) extends KVStoreA[Unit] case class Get[T](key: String) extends KVStoreA[Option[T]] case class Del(key: String) extends KVStoreA[Unit] type KVStore[A] = Free[KVStoreA,A] object KVStoreA { def put[T](key: String, value: T): KVStore[Unit] = Free.liftF[KVStoreA,Unit](Put[T](key,value)) def get[T](key: String): KVStore[Option[T]] = Free.liftF[KVStoreA,Option[T]](Get[T](key)) def del(key: String): KVStore[Unit] = Free.liftF[KVStoreA,Unit](Del(key)) def mod[T](key: String, f: T => T): KVStore[Unit] = for { opt <- get[T](key) _ <- opt.map {t => put[T](key,f(t))}.getOrElse(Free.pure(())) } yield() } } object DSLs { import ADTs._ import KVStoreA._ def prg: KVStore[Option[Int]] = for { _ <- put[Int]("wild-cats", 2) _ <- mod[Int]("wild-cats", (_ + 12)) _ <- put[Int]("tame-cats", 5) n <- get[Int]("wild-cats") _ <- del("tame-cats") } yield n } object IMPLs { import ADTs._ import cats.{~>} import cats.data.State type KVStoreState[A] = State[Map[String, Any], A] val kvsToState: KVStoreA ~> KVStoreState = new (KVStoreA ~> KVStoreState) { def apply[A](fa: KVStoreA[A]): KVStoreState[A] = fa match { case Put(key, value) => State { (s:Map[String, Any]) => (s.updated(key, value),()) } case Get(key) => State { (s:Map[String, Any]) => (s,s.get(key).asInstanceOf[A]) } case Del(key) => State { (s:Map[String, Any]) => (s - key, (())) } } } } import ADTs._,DSLs._,IMPLs._ val prgRunner = prg.foldMap(kvsToState) prgRunner.run(Map.empty).value import cats.{Monad,RecursiveTailRecM} implicitly[Monad[KVStoreState]] implicitly[RecursiveTailRecM[KVStoreState]]}
- Cats(1)- 从Free开始,Free cats
- Cats(2)- Free语法组合,Coproduct-ADT composition
- Cats(3)- freeK-Free编程更轻松,Free programming with freeK
- Cats(4)- 叠加Free程序运算结果,Stacking monadic result types
- Flying cats
- poj_3735_Training little cats(矩阵快速幂)
- free.
- free
- free()
- free
- free
- free
- free
- free()
- free
- free
- free
- free
- 云时代的分布式数据库:阿里分布式数据库服务DRDS
- Docker学习笔记2: Docker 概述
- WebView 实现 NestedScrollingChild
- Kubernetes用户指南(三)--在生产环境中使用Pod来工作、管理部署
- BigDecimal(一)
- Cats(1)- 从Free开始,Free cats
- Linux32位64位问题
- 项目应用:pojo与map的相互转换
- Spring+Mybatis实战教程
- java实现压缩指定文件夹(文件夹包含子文件夹或文件)为zip格式压缩包
- 滑动删除与cell中的点击事件冲突
- thinking in java test5.5练习(10)(11)(12)finalize()方法
- Kubernetes入门(三) - 网络
- spring的管理bean和依赖注入的原理剖析