Scalaz(18)- Monad: ReaderWriterState-可以是一种简单的编程语言
说道FP,我们马上会联想到Monad。我们说过Monad的代表函数flatMap可以把两个运算F[A],F[B]连续起来,这样就可以从程序的意义上形成一种串型的流程(workflow)。更直白的讲法是:任何类型只要实现了flatMap就可以用for-comprehension, for{...}yield。在这个for{...}里我们可以好像OOP一样编写程序。这个for就是一种运算模式,它规范了在for{...}里指令的行为。我们正从OOP风格走入FP编程模式,希望有个最基本的FP编程模式使我们能够沿用OOP编程风格的语法和思维。Monad应该就是最合适的泛函数据类型了。我们先从最基本的开始:假如我们有一段行令程序:
/*
val a = e1
val b = e2(a)
val c = e3(a,b)
val d = e2(c)
*/
通过这些函数e1,e2,e3最后计算出d值。如果是用FP风格来编这段程序的话,首先我们必须把函数的结果d放入F[d]的F里。F就是上面所说的运算模式,在这里可以用大家熟悉的context(上下文)来表示。F必须是个Monad,F[]相当于for{...}yield。我们先试试用Id,虽然Id[A]对A不做任何处理,直接返回,好像没什么意义,但这种类型具备了map和flatMap,应该可以用for-comprehension:
import scalaz._
import Scalaz._
def e1:Id[Int] = //> e1: => scalaz.Scalaz.Id[Int]
def e2(a: Int): Id[Int] = a + //> e2: (a: Int)scalaz.Scalaz.Id[Int]
def e3(a: Int, b: Int): Id[Int] = a + b //> e3: (a: Int, b: Int)scalaz.Scalaz.Id[Int]
for {
a <- e1
b <- e2(a)
c <- e3(a,b)
d <- e2(c)
} yield d //> res0: scalaz.Scalaz.Id[Int] = 22
可以看到,在for-loop里就是OOP的行令程序。不过如果觉着这个Id没什么意义,可以试试Option看:
import scalaz._
import Scalaz._
def e1:Option[Int] = .some //> e1: => Option[Int]
def e2(a: Int): Option[Int] = (a + ).some //> e2: (a: Int)Option[Int]
def e3(a: Int, b: Int): Option[Int] = (a + b).some//> e3: (a: Int, b: Int)Option[Int]
for {
a <- e1
b <- e2(a)
c <- e3(a,b)
d <- e2(c)
} yield d //> res0: Option[Int] = Some(22)
看,虽然换了个壳子(context), 但for-loop里的程序没有变化。换一句话讲就是for-loop里的程序根本不理会包裹的context。
Reader也是一种Monad,用它又怎样呢:
import scalaz._
import Scalaz._
def e1:Reader[Int,Int] = Reader[Int,Int](a => a) //> e1: => scalaz.Reader[Int,Int]
def e2(a: Int): Reader[Int,Int] = Reader[Int,Int](_ => a + )
//> e2: (a: Int)scalaz.Reader[Int,Int]
def e3(a: Int, b: Int): Reader[Int, Int] = Reader[Int,Int](_ => a+b)
//> e3: (a: Int, b: Int)scalaz.Reader[Int,Int]
val prg = for {
a <- e1
b <- e2(a)
c <- e3(a,b)
d <- e2(c)
} yield d //> prg : scalaz.Kleisli[scalaz.Id.Id,Int,Int] = Kleisli(<function1>)
prg.run() //> res0: scalaz.Id.Id[Int] = 22
虽然在语法上有些蹩脚,但还是证明了for-loop里的程序是不理会外面context的。那么我们可不可以说这个prg就是一个简单的FP编程语言。它把运算结果放在context里,直至运行了某种interpreter才能取得实际的运算值(用run(10)得到22)。当然,一段程序,它的运算行为受制于单一种类型的context可能有些弱了。如果需要获得一种可用的FP编程语言,我们可能还是要探讨如何把单一类型context组合成多类型混合的context。
我们发现在scalaz里有些type class的名称是以T结束的如:ReaderT,WriterT,StateT等等。这个T指的是变形器Transformer,意思是用它可以堆砌(stacking)context。看看StateT,简单定义应该是这样的:
case class StateT[F[_],S,A](run: S => F[(S,A)])
我们可以把F类堆砌在State上。实践证明如果这个F实现了flatMap,那么堆砌成的类型也能实现flatMap。好,scalaz的Option是实现了flatMap的,那么能不能把它和State堆砌在一起呢?堆砌而成的context会有什么效果呢?我们先看看单一Option和State作为一种context的效果:
for {
a <- .some
b <- (None: Option[Int])
c <- .some
} yield c //> res1: Option[Int] = None
val statePrg = for {
a <- get[Int]
b <- State[Int,Int](s => (s, s + a))
_ <- put()
} yield b //> statePrg : scalaz.IndexedStateT[scalaz.Id.Id,Int,Int,Int] = scalaz.IndexedS
//| tateT$$anon$10@15ff3e9e
statePrg.run() //> res2: scalaz.Id.Id[(Int, Int)] = (9,6)
依我来看,Option主要效果是在遇到None值时立即退出。而State的主要作用是在运算同时可以维护一个状态。那么如果把Option和State叠加起来就会同时具备这两种类型的特点了吧?也就是既能维护状态又能在遇到None值时立即终止运算退出了。首先验证一下用Option的flatMap来实现叠加context的flatMap:
case class OptionState[S,A](run: S => Option[(S,A)]) {
def map[B](f: A => B): OptionState[S,B] =
OptionState {
s => run(s) map { case (s1,a1) => (s1,f(a1)) }
}
def flatMap[B](f: A => OptionState[S,B]): OptionState[S,B] =
OptionState {
s => run(s) flatMap { case (s1,a1) => f(a1).run(s1) }
}
}
是的,我们可以用Option的map和flatMap来实现OptionState的map和flatMap。当然,如果我们想在一个for-comprehension里同时使用Option和State就必须把它们升格成OptionState类型:
def liftOption[S,A](oa: Option[A]): OptionState[S,A] = oa match {
case Some(a) => OptionState {s => (s,a).some }
case None => OptionState {_ => none}
}
def liftState[S,A](sa: State[S,A]): OptionState[S,A] =
OptionState {s => sa(s).some}
现在试试用叠加效果的for-comprehension:
val osprg: OptionState[Int,Int] = for {
a <- liftOption(.some)
b <- liftState(put(a))
c <- liftState(get[Int])
d <- liftState(State[Int,Int](s => (s+c, s+a)))
} yield c //> osprg : Exercises.rws.OptionState[Int,Int] = OptionState(<function1>)
osprg.run() //> res3: Option[(Int, Int)] = Some((6,3))
val osprg1: OptionState[Int,Int] = for {
a <- liftOption(.some)
b <- liftState(put(a))
_ <- liftOption((None: Option[Int]))
c <- liftState(get[Int])
d <- liftState(State[Int,Int](s => (s+c, s+a)))
} yield c //> osprg1 : Exercises.rws.OptionState[Int,Int] = OptionState(<function1>)
osprg1.run() //> res4: Option[(Int, Int)] = None
看,既可以维护状态又具备None处理机制。
好了,scalaz里有个ReaderWriterState这么个type class,就是一个Reader+Writer+State堆砌的Monad。相信scalaz特别提供了这么个type class应该有它的用意。我的猜想是这个Monad是个功能比较完整的组合Monad。作为for-comprehension的context应该能提供比较全面的效果。从字意上解释就是在由它形成的Monadic编程语言里可以同时提供运算(compute)、跟踪(logging)和状态维护功能。它的基础类型是IndexedReaderWriterStateT:scalaz/package.scala
type ReaderWriterStateT[F[_], -R, W, S, A] = IndexedReaderWriterStateT[F, R, W, S, S, A]
object ReaderWriterStateT extends ReaderWriterStateTInstances with ReaderWriterStateTFunctions {
def apply[F[_], R, W, S, A](f: (R, S) => F[(W, A, S)]): ReaderWriterStateT[F, R, W, S, A] = IndexedReaderWriterStateT[F, R, W, S, S, A] { (r: R, s: S) => f(r, s) }
}
type IndexedReaderWriterState[-R, W, -S1, S2, A] = IndexedReaderWriterStateT[Id, R, W, S1, S2, A]
object IndexedReaderWriterState extends ReaderWriterStateTInstances with ReaderWriterStateTFunctions {
def apply[R, W, S1, S2, A](f: (R, S1) => (W, A, S2)): IndexedReaderWriterState[R, W, S1, S2, A] = IndexedReaderWriterStateT[Id, R, W, S1, S2, A] { (r: R, s: S1) => f(r, s) }
}
type ReaderWriterState[-R, W, S, A] = ReaderWriterStateT[Id, R, W, S, A]
object ReaderWriterState extends ReaderWriterStateTInstances with ReaderWriterStateTFunctions {
def apply[R, W, S, A](f: (R, S) => (W, A, S)): ReaderWriterState[R, W, S, A] = IndexedReaderWriterStateT[Id, R, W, S, S, A] { (r: R, s: S) => f(r, s) }
}
type IRWST[F[_], -R, W, -S1, S2, A] = IndexedReaderWriterStateT[F, R, W, S1, S2, A]
val IRWST: IndexedReaderWriterStateT.type = IndexedReaderWriterStateT
type IRWS[-R, W, -S1, S2, A] = IndexedReaderWriterState[R, W, S1, S2, A]
val IRWS: IndexedReaderWriterState.type = IndexedReaderWriterState
type RWST[F[_], -R, W, S, A] = ReaderWriterStateT[F, R, W, S, A]
val RWST: ReaderWriterStateT.type = ReaderWriterStateT
type RWS[-R, W, S, A] = ReaderWriterState[R, W, S, A]
val RWS: ReaderWriterState.type = ReaderWriterState
如果把Reader,Writer,State款式分开来对比分析的话:
case class Reader[R, A](f: R => A) //传入R,返回A后不理会R
case class Writer[W, A](w: (W, A)) //直接返回W,A
case class State[S, A](f: S => (A, S)) //传入S, 返回A和S
那么把以上三个结合起来后它的款式应该是这样的了吧:
case class ReaderWriterState[R, W, S, A](
run: (R, S) => (W, A, S) //传入R,S 返回W,A,S
)
case class ReaderWriterStateT[F[_],R, W, S, A](
run: (R, S) => F[(W, A, S)] //传入R,S 返回W,A,S。只是包在了F内
)
传入的和返回的类型是匹配的。在scalaz里是这样定义的:scalaz/ReaderWriterStateT.scala
/** A monad transformer stack yielding `(R, S1) => F[(W, A, S2)]`. */
sealed abstract class IndexedReaderWriterStateT[F[_], -R, W, -S1, S2, A] {
self =>
def run(r: R, s: S1): F[(W, A, S2)] /** Discards the writer component. */
def state(r: R)(implicit F: Functor[F]): IndexedStateT[F, S1, S2, A] =
IndexedStateT((s: S1) => F.map(run(r, s)) {
case (w, a, s1) => (s1, a)
}) /** Calls `run` using `Monoid[S].zero` as the initial state */
def runZero[S <: S1](r: R)(implicit S: Monoid[S]): F[(W, A, S2)] =
run(r, S.zero) /** Run, discard the final state, and return the final value in the context of `F` */
def eval(r: R, s: S1)(implicit F: Functor[F]): F[(W, A)] =
F.map(run(r,s)) { case (w,a,s2) => (w,a) } /** Calls `eval` using `Monoid[S].zero` as the initial state */
def evalZero[S <: S1](r:R)(implicit F: Functor[F], S: Monoid[S]): F[(W,A)] =
eval(r,S.zero) /** Run, discard the final value, and return the final state in the context of `F` */
def exec(r: R, s: S1)(implicit F: Functor[F]): F[(W,S2)] =
F.map(run(r,s)){case (w,a,s2) => (w,s2)} /** Calls `exec` using `Monoid[S].zero` as the initial state */
def execZero[S <: S1](r:R)(implicit F: Functor[F], S: Monoid[S]): F[(W,S2)] =
exec(r,S.zero)
...
我们看到IndexedReaderWriterStateT已经实现了很多IndexedStateT的运算方法如:eval,exec等。看看它的map和flatMap是怎么实现的:
def map[B](f: A => B)(implicit F: Functor[F]): IndexedStateT[F, S1, S2, B] = IndexedStateT(s => F.map(apply(s)) {
case (s1, a) => (s1, f(a))
})
def flatMap[S3, B](f: A => IndexedStateT[F, S2, S3, B])(implicit F: Bind[F]): IndexedStateT[F, S1, S3, B] = IndexedStateT(s => F.bind(apply(s)) {
case (s1, a) => f(a)(s1)
})
与我们前面所做的OptionState例子一样:如果F能实现map和flatMap则IndexedReaderWriterStateT就能实现map和flatMap。为了省却在for-loop里每行命令都使用lift进行类型升格,IndexedReaderWriterStateT重新实现了大部分操作函数:
private trait ReaderWriterStateTMonad[F[_], R, W, S]
extends MonadReader[({type λ[r, α]=ReaderWriterStateT[F, r, W, S, α]})#λ, R]
with MonadState[({type f[s, α] = ReaderWriterStateT[F, R, W, s, α]})#f, S]
with MonadListen[({type f[w, α] = ReaderWriterStateT[F, R, w, S, α]})#f, W]
with IndexedReaderWriterStateTFunctor[F, R, W, S, S] {
implicit def F: Monad[F]
implicit def W: Monoid[W] def bind[A, B](fa: ReaderWriterStateT[F, R, W, S, A])(f: A => ReaderWriterStateT[F, R, W, S, B]): ReaderWriterStateT[F, R, W, S, B] = fa flatMap f
def point[A](a: => A): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((_, s) => F.point((W.zero, a, s)))
def ask: ReaderWriterStateT[F, R, W, S, R] =
ReaderWriterStateT((r, s) => F.point((W.zero, r, s)))
def local[A](f: R => R)(fa: ReaderWriterStateT[F, R, W, S, A]): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((r, s) => fa.run(f(r), s))
override def scope[A](k: R)(fa: ReaderWriterStateT[F, R, W, S, A]): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((_, s) => fa.run(k, s))
override def asks[A](f: R => A): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((r, s) => F.point((W.zero, f(r), s)))
def init: ReaderWriterStateT[F, R, W, S, S] =
ReaderWriterStateT((_, s) => F.point((W.zero, s, s)))
def get = init
def put(s: S): ReaderWriterStateT[F, R, W, S, Unit] =
ReaderWriterStateT((r, _) => F.point((W.zero, (), s)))
override def modify(f: S => S): ReaderWriterStateT[F, R, W, S, Unit] =
ReaderWriterStateT((r, s) => F.point((W.zero, (), f(s))))
override def gets[A](f: S => A): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((_, s) => F.point((W.zero, f(s), s)))
def writer[A](w: W, v: A): ReaderWriterStateT[F, R, W, S, A] =
ReaderWriterStateT((_, s) => F.point((w, v, s)))
override def tell(w: W): ReaderWriterStateT[F, R, W, S, Unit] =
ReaderWriterStateT((_, s) => F.point((w, (), s)))
def listen[A](ma: ReaderWriterStateT[F, R, W, S, A]): ReaderWriterStateT[F, R, W, S, (A, W)] =
ReaderWriterStateT((r, s) => F.map(ma.run(r, s)) { case (w, a, s1) => (w, (a, w), s1)})
}
我们示范用这个ReaderWriterState来写一段程序:模拟一段通讯端口使用程序并把使用情况记录下来。先传入一个端口号,在程序中可以重设使用的端口号:
val program: ReaderWriterState[Config, List[String], Int, Int] = for {
_ <- log("Start - r: %s, s: %s")
res <- invokeService
_ <- log("Between - r: %s, s: %s")
_ <- setService(,"Com8")
_ <- invokeService
_ <- log("Done - r: %s, s: %s")
} yield res //> program : scalaz.RWS[Exercises.rws.Config,List[String],Int,Int] = scalaz.I
//| ndexedReaderWriterStateT$$anon$5@223191a6
这倒像是一段高级语言写的程序。细节都在几个功能函数里。它们都必须返回ReaderWriterState类型:
case class Config(var port: Int, var portName: String)
def log[R, S](msg: String): RWS[R, List[String], S, Unit] =
ReaderWriterState {
case (r, s) => (msg.format(r, s) :: Nil, (), s) //.point[Identity]
} //> log: [R, S](msg: String)scalaz.RWS[R,List[String],S,Unit]
def invokeService: ReaderWriterState[Config, List[String], Int, Int] =
ReaderWriterState {
case (cfg, invocationCount) => (
List("Invoking service with port: " + cfg.portName),
scala.util.Random.nextInt(),
invocationCount +
) //.point[Identity]
} //> invokeService: => scalaz.ReaderWriterState[Exercises.rws.Config,List[String
//| ],Int,Int]
def setService(p: Int, n: String): ReaderWriterState[Config, List[String], Int, Int] =
ReaderWriterState {
case (cfg, invocationCount) => cfg.port=p; cfg.portName=n
(List("Changing service port to " + cfg.portName),
scala.util.Random.nextInt(),
invocationCount)
} //> setService: (p: Int, n: String)scalaz.ReaderWriterState[Exercises.rws.Confi
//| g,List[String],Int,Int] val program: ReaderWriterState[Config, List[String], Int, Int] = for {
_ <- log("Start - r: %s, s: %s")
res <- invokeService
_ <- log("Between - r: %s, s: %s")
_ <- setService(,"Com8")
_ <- invokeService
_ <- log("Done - r: %s, s: %s")
} yield res //> program : scalaz.RWS[Exercises.rws.Config,List[String],Int,Int] = scalaz.I
//| ndexedReaderWriterStateT$$anon$5@223191a6
val r = program run (Config(,"Com3"), ) //> r : scalaz.Id.Id[(List[String], Int, Int)] = (List(Start - r: Config(443,C
//| om3), s: 0, Invoking service with port: Com3, Between - r: Config(443,Com3)
//| , s: 1, Changing service port to Com8, Invoking service with port: Com8, Do
//| ne - r: Config(88,Com8), s: 2),68,2)
println("Result: " + r._2) //> Result: 68
println("Service invocations: " + r._3) //> Service invocations: 2
println("Log: %n%s".format(r._1.mkString("\t", "%n\t".format(), "")))
//> Log:
//| Start - r: Config(443,Com3), s: 0
//| Invoking service with port: Com3
//| Between - r: Config(443,Com3), s: 1
//| Changing service port to Com8
//| Invoking service with port: Com8
//| Done - r: Config(88,Com8), s: 2
Scalaz(18)- Monad: ReaderWriterState-可以是一种简单的编程语言的更多相关文章
- 使用strace工具故障排查的5种简单方法
使用strace工具故障排查的5种简单方法 本文源自5 simple ways to troubleshoot using strace strace 是一个非常简单的工具,用来跟踪可执行程序的系统调 ...
- 【转】10种简单的Java性能优化
10种简单的Java性能优化 2015/06/23 | 分类: 基础技术 | 14 条评论 | 标签: 性能优化 分享到: 本文由 ImportNew - 一直在路上 翻译自 jaxenter.欢迎加 ...
- 【页面传值6种方式】- 【JSP 页面传值方法总结:4种】 - 【跨页面传值的几种简单方式3种】
阅读目录 1. URL 链接后追加参数 2. Form 3. 设置 Cookie 4. 设置 Session JSP 页面间传递参数是项目中经常需要的,这应该算是 web 基本功吧. 试着将各种方式总 ...
- 一种简单,轻量,灵活的C#对象转Json对象的方案(续)
本文参考资料 一种简单,轻量,灵活的C#对象转Json对象的方案 [源码]Literacy 快速反射读写对象属性,字段 一段废话 之前我已经介绍了这个方案的名称为JsonBuilder,这套方案最大的 ...
- Xamarin.Android之Splash的几种简单实现
对现在的APP软件来说,基本上都会有一个Splash页面,类似大家常说的欢迎页面.启动界面之类的. 正常来说这个页面都会有一些相关的信息,比如一些理念,Logo,版本信息等 下面就来看看在Xamari ...
- asp.net mvc 中 一种简单的 URL 重写
asp.net mvc 中 一种简单的 URL 重写 Intro 在项目中想增加一个公告的功能,但是又不想直接用默认带的那种路由,感觉好low逼,想弄成那种伪静态化的路由 (别问我为什么不直接静态化, ...
- Honeywords项目——检查密码是否被破解的一种简单方法
Honeywords项目使用一种简单的方法来改进hash后的密码的安全性——为每个账户维护一个额外的honeywords(假密码).如果有黑客拿到了密码的文件,然后试图用brute froce的方式破 ...
- JavaScript 一种轻量级的编程语言
JavaScript 一种轻量级的编程语言 作为一名计算机应用专业的学生,大一上学期开始接触了网页设计和制作,刚开始时感觉做网页很不错,简单地写几行代码就能做出效果来,当时感觉很兴奋,渐渐的喜欢上它 ...
- iOS开发UI篇—iOS开发中三种简单的动画设置
iOS开发UI篇—iOS开发中三种简单的动画设置 [在ios开发中,动画是廉价的] 一.首尾式动画 代码示例: // beginAnimations表示此后的代码要“参与到”动画中 [UIView b ...
随机推荐
- js里slice,substr和substring的区别
概要: string.slice(start, end)提取一个字符串 string.substring(start, end)提取一个字符串,end不支持负数 string.substr(start ...
- Atitit 马尔可夫过程(Markov process) hmm隐马尔科夫。 马尔可夫链,的原理attilax总结
Atitit 马尔可夫过程(Markov process) hmm隐马尔科夫. 马尔可夫链,的原理attilax总结 1. 马尔可夫过程1 1.1. 马尔科夫的应用 生成一篇"看起来像文章的 ...
- atitit.vod search doc.doc 点播系统搜索功能设计文档
atitit.vod search doc.doc 点播系统搜索功能设计文档 按键的enter事件1 Left rig事件1 Up down事件2 key_events.key_search = fu ...
- Netty学习三:线程模型
1 Proactor和Reactor Proactor和Reactor是两种经典的多路复用I/O模型,主要用于在高并发.高吞吐量的环境中进行I/O处理. I/O多路复用机制都依赖于一个事件分发器,事件 ...
- hibernate(四) 双向多对多映射关系
序言 莫名长了几颗痘,真TM疼,可能是现在运动太少了,天天对着电脑,决定了,今天下午花两小时去跑步了, 现在继上一章节的一对多的映射关系讲解后,今天来讲讲多对多的映射关系把,明白了一对多,多对多个人感 ...
- Javascript函数中的高级运用
先介绍一下js中的高阶函数,所谓的高阶函数就是,一个函数中的参数是一个函数或者返回的是一个函数,就称为高阶函数. js中已经提高了一下高阶函数,使用起来非常棒,当然我们也可以自己实现,我介绍几种ES5 ...
- 为什么SQL语句Where 1=1 and在SQL Server中不影响性能
最近一个朋友和我探讨关于Where 1=1 and这种形式的语句会不会影响性能.最后结论是不影响. 虽然结论正确,但对问题的认识却远远没有解决问题的根本.实际上在T-SQL语句的书写过 ...
- CSS选择器、CSS hack及CSS执行效率
主要内容: 1.CSS选择器.优先级与匹配原理 2. CSS 引入的方式有哪些 ? link 和 @import 的区别是 ? 3.CSS hack 4.如何书高效CSS 一.CSS选择器.优先级与 ...
- ObjectOutputStream和ObjectInputStream
官方解释: ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream.可以使用 ObjectInputStream 读取(重构)对象.通过使用流中的文 ...
- 使用python selenium webdriver模拟浏览器
selenium是进行web自动化测试的一个工具,支持C,C++,Python,Java等语言,他能够实现模拟手工操作浏览器,进行自动化,通过webdriver驱动浏览器操作,我使用的是chrome浏 ...