说道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-可以是一种简单的编程语言的更多相关文章

  1. 使用strace工具故障排查的5种简单方法

    使用strace工具故障排查的5种简单方法 本文源自5 simple ways to troubleshoot using strace strace 是一个非常简单的工具,用来跟踪可执行程序的系统调 ...

  2. 【转】10种简单的Java性能优化

    10种简单的Java性能优化 2015/06/23 | 分类: 基础技术 | 14 条评论 | 标签: 性能优化 分享到: 本文由 ImportNew - 一直在路上 翻译自 jaxenter.欢迎加 ...

  3. 【页面传值6种方式】- 【JSP 页面传值方法总结:4种】 - 【跨页面传值的几种简单方式3种】

    阅读目录 1. URL 链接后追加参数 2. Form 3. 设置 Cookie 4. 设置 Session JSP 页面间传递参数是项目中经常需要的,这应该算是 web 基本功吧. 试着将各种方式总 ...

  4. 一种简单,轻量,灵活的C#对象转Json对象的方案(续)

    本文参考资料 一种简单,轻量,灵活的C#对象转Json对象的方案 [源码]Literacy 快速反射读写对象属性,字段 一段废话 之前我已经介绍了这个方案的名称为JsonBuilder,这套方案最大的 ...

  5. Xamarin.Android之Splash的几种简单实现

    对现在的APP软件来说,基本上都会有一个Splash页面,类似大家常说的欢迎页面.启动界面之类的. 正常来说这个页面都会有一些相关的信息,比如一些理念,Logo,版本信息等 下面就来看看在Xamari ...

  6. asp.net mvc 中 一种简单的 URL 重写

    asp.net mvc 中 一种简单的 URL 重写 Intro 在项目中想增加一个公告的功能,但是又不想直接用默认带的那种路由,感觉好low逼,想弄成那种伪静态化的路由 (别问我为什么不直接静态化, ...

  7. Honeywords项目——检查密码是否被破解的一种简单方法

    Honeywords项目使用一种简单的方法来改进hash后的密码的安全性——为每个账户维护一个额外的honeywords(假密码).如果有黑客拿到了密码的文件,然后试图用brute froce的方式破 ...

  8. JavaScript 一种轻量级的编程语言

     JavaScript 一种轻量级的编程语言 作为一名计算机应用专业的学生,大一上学期开始接触了网页设计和制作,刚开始时感觉做网页很不错,简单地写几行代码就能做出效果来,当时感觉很兴奋,渐渐的喜欢上它 ...

  9. iOS开发UI篇—iOS开发中三种简单的动画设置

    iOS开发UI篇—iOS开发中三种简单的动画设置 [在ios开发中,动画是廉价的] 一.首尾式动画 代码示例: // beginAnimations表示此后的代码要“参与到”动画中 [UIView b ...

随机推荐

  1. js里slice,substr和substring的区别

    概要: string.slice(start, end)提取一个字符串 string.substring(start, end)提取一个字符串,end不支持负数 string.substr(start ...

  2. Atitit 马尔可夫过程(Markov process) hmm隐马尔科夫。 马尔可夫链,的原理attilax总结

    Atitit 马尔可夫过程(Markov process) hmm隐马尔科夫. 马尔可夫链,的原理attilax总结 1. 马尔可夫过程1 1.1. 马尔科夫的应用 生成一篇"看起来像文章的 ...

  3. atitit.vod search doc.doc 点播系统搜索功能设计文档

    atitit.vod search doc.doc 点播系统搜索功能设计文档 按键的enter事件1 Left rig事件1 Up down事件2 key_events.key_search = fu ...

  4. Netty学习三:线程模型

    1 Proactor和Reactor Proactor和Reactor是两种经典的多路复用I/O模型,主要用于在高并发.高吞吐量的环境中进行I/O处理. I/O多路复用机制都依赖于一个事件分发器,事件 ...

  5. hibernate(四) 双向多对多映射关系

    序言 莫名长了几颗痘,真TM疼,可能是现在运动太少了,天天对着电脑,决定了,今天下午花两小时去跑步了, 现在继上一章节的一对多的映射关系讲解后,今天来讲讲多对多的映射关系把,明白了一对多,多对多个人感 ...

  6. Javascript函数中的高级运用

    先介绍一下js中的高阶函数,所谓的高阶函数就是,一个函数中的参数是一个函数或者返回的是一个函数,就称为高阶函数. js中已经提高了一下高阶函数,使用起来非常棒,当然我们也可以自己实现,我介绍几种ES5 ...

  7. 为什么SQL语句Where 1=1 and在SQL Server中不影响性能

        最近一个朋友和我探讨关于Where 1=1 and这种形式的语句会不会影响性能.最后结论是不影响.     虽然结论正确,但对问题的认识却远远没有解决问题的根本.实际上在T-SQL语句的书写过 ...

  8. CSS选择器、CSS hack及CSS执行效率

    主要内容: 1.CSS选择器.优先级与匹配原理 2. CSS 引入的方式有哪些 ? link 和 @import 的区别是 ? 3.CSS hack 4.如何书高效CSS  一.CSS选择器.优先级与 ...

  9. ObjectOutputStream和ObjectInputStream

    官方解释: ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream.可以使用 ObjectInputStream 读取(重构)对象.通过使用流中的文 ...

  10. 使用python selenium webdriver模拟浏览器

    selenium是进行web自动化测试的一个工具,支持C,C++,Python,Java等语言,他能够实现模拟手工操作浏览器,进行自动化,通过webdriver驱动浏览器操作,我使用的是chrome浏 ...