泛函编程(14)-try to map them all

时间:2022-03-29 00:25:37

虽然明白泛函编程风格中最重要的就是对一个管子里的元素进行操作。这个管子就是这么一个东西:F[A],我们说F是一个针对元素A的高阶类型,其实F就是一个装载A类型元素的管子,A类型是相对低阶,或者说是基础的类型。泛函编程风格就是在F内部用对付A类的函数对里面的元素进行操作。但在之前现实编程中确总是没能真正体会这种编程模式畅顺的用法:到底应该在哪里用?怎么用?可能内心里还是没能摆脱OOP的思维方式吧。在前面Stream设计章节里,我们采用了封装形式的数据结构设计,把数据结构uncons放进了特质申明里:

   trait Stream[+A] {
def uncons: Option[(A, Stream[A])]
def isEmpty: Boolean = uncons.isEmpty
}
object Stream {
def empty[A]: Stream[A] = new Stream[A] {
def uncons = None
}
def cons[A](h: => A, t: => Stream[A]): Stream[A] = new Stream[A] {
def uncons = Some((h,t))
}
def apply[A](as: A*): Stream[A] = {
if (as.isEmpty) empty
else cons(as.head, apply(as.tail: _*))
} }

我们用tuple(A, Stream[A])来代表一个完整的Stream并把它放进一个Option里,本意是空的Stream就可以用None来表示。这个Option就像是那个附加的套子把我们的目标类型(A, Stream[A])套成了F[A]类型。其实我们的目的是对管子里的A类型进行操作,特别是对A类型元素进行模式匹配。但是在之前的设计里我们却对F[A]这个戴着套子的类型进行了模式匹配。静下来回顾一下觉着还是必须想办法尽量多用些泛函的方式来做。

先看看这个map函数,我们在前面曾经为Option编写了这个函数:(oa:Option[A]).map[B](f: A => B): Option[B]。我们可以向map传入一个操作A级别类型的函数,比如一段A级别类型的模式匹配方式代码。Option map返回的结果是Option[B],是一个高阶类型,但我们可以很方便的用getOrElse来取得这个返回Option里面的元素。看个例子比较一下:

       //戴着套子进行模式匹配
def toList: List[A] = uncons match {
case None => Nil
case Some((h,t)) => h :: t.toList
}
//用map操作
def toList: List[A] = uncons.map {
case (h,t) => h :: t.toList
} getOrElse(Nil)

从以上例子可以看出:通过使用map,用元素类型级别模式匹配,然后用getOrElse取出。Stream为空时采用getOrElse默认值。可以让代码更简洁易名。
看多几个例子:

     //戴着套子
def take(n: Int): Stream[A] = {
if ( n == 0 ) empty
else
uncons match {
case None => empty
case Some((h,t)) => cons(h,t.take(n-1))
}
}
//用map操作
def take(n: Int): Stream[A] = {
if ( n == 0 ) empty
else
uncons map {
case (h,t) => cons(h,t.take(n-1))
} getOrElse(empty)
}
//戴着套子
def takeWhile(f: A => Boolean): Stream[A] = {
uncons match {
case None => empty
case Some((h,t)) => if ( f(h) ) cons(h,t.takeWhile(f)) else empty
}
}
//用map操作
def takeWhile(f: A => Boolean): Stream[A] = {
uncons map {
case (h,t) => if ( f(h) ) cons(h,t.takeWhile(f)) else empty
} getOrElse empty
}
//高阶类型操作
def foldRight[B](z: B)(op: (A, => B) => B): B = {
uncons match {
case None => z
case Some((h,t)) => op(h,t.foldRight(z)(op))
}
}
//monadic style
def foldRight[B](z: B)(op: (A, => B) => B): B = {
uncons map {
case (h,t) => op(h,t.foldRight(z)(op))
} getOrElse z
}

嗯,改变操作方式时共性很明显。
再看看下面的例子,如果不用map的话会是多么的混乱:

     //没用map方式
def unfold[A,S](z: S)(f: S => Option[(A,S)]): Stream[A] ={
f(z) match {
case None => empty
case Some((a,s)) => cons(a,unfold(s)(f))
}
}
def mapByUnfold[B](f: A => B): Stream[B] = {
unfold(uncons) {
case Some((h,t)) => Some((f(h),Some((t.headOption.getOrElse(h), t.tail.tailOption.getOrElse(empty)))))
case _ => None
}
}
def zipWithByUnfold[B,C](b: Stream[B])(f: (A,B) => C): Stream[C] = {
unfold((uncons,b.uncons)) {
case (Some((ha,ta)),Some((hb,tb))) => Some(f(ha,hb),(Some((ta.head,ta.tail)),Some((tb.head,tb.tail))))
case _ => None
}
}

看上面这些代码,由于传入unfold的函数f的返回结果是个高阶类型Option,这使得整体表达形式不但臃肿,更乱还很难看得懂。试着用map改写这些函数:

     def unfoldWithMap[A,S](z: S)(f: S => Option[(A,S)]): Stream[A] ={
f(z) map {
case (a,s) => cons(a,unfold(s)(f))
} getOrElse empty
}
def mapByUnfoldWithMap[B](f: A => B): Stream[B] = {
unfold(this) { s =>
this.uncons map {
case (h,t) => (f(h),t)
}
}
}

看起来简洁多了。另外一个用了flatMap:

          def zipWithByUnfoldWithMap[B,C](b: Stream[B])(f: (A,B) => C): Stream[C] = {
//起始状态是tuple(Stream[A],Stream[B]),状态转换函数>>> (s1,s2) => Option(a, (s1,s2))
unfold((this,b)) { s => {
for {
a <- s._1.uncons //用flatMap从Option[(A,Stream[A])]取出元素 >>> (A,Stream[A])
b <- s._2.uncons //用flatMap从Option[(B,Stream[B])]取出元素 >>> (B,Stream[B])
} yield {
( f(a._1, b._1), (a._2, b._2) ) //返回新的状态:C >>> (f(a,b),(ta,tb))
}
}
}
}

乍看起来好像挺复杂,但尝试去理解代码的意义,上面一段代码会更容易理解一点。
中间插播了一段map,flatMap的示范,目的是希望在后面的设计思考中向泛函编程风格更靠近一点。

泛函编程(14)-try to map them all的更多相关文章

  1. 泛函编程(38)-泛函Stream IO:IO Process in action

    在前面的几节讨论里我们终于得出了一个概括又通用的IO Process类型Process[F[_],O].这个类型同时可以代表数据源(Source)和数据终端(Sink).在这节讨论里我们将针对Proc ...

  2. 泛函编程(36)-泛函Stream IO:IO数据源-IO Source &amp&semi; Sink

    上期我们讨论了IO处理过程:Process[I,O].我们说Process就像电视信号盒子一样有输入端和输出端两头.Process之间可以用一个Process的输出端与另一个Process的输入端连接 ...

  3. 泛函编程(35)-泛函Stream IO:IO处理过程-IO Process

    IO处理可以说是计算机技术的核心.不是吗?使用计算机的目的就是希望它对输入数据进行运算后向我们输出计算结果.所谓Stream IO简单来说就是对一串按序相同类型的输入数据进行处理后输出计算结果.输入数 ...

  4. 泛函编程(34)-泛函变量:处理状态转变-ST Monad

    泛函编程的核心模式就是函数组合(compositionality).实现函数组合的必要条件之一就是参与组合的各方程序都必须是纯代码的(pure code).所谓纯代码就是程序中的所有表达式都必须是Re ...

  5. 泛函编程(32)-泛函IO:IO Monad

    由于泛函编程非常重视函数组合(function composition),任何带有副作用(side effect)的函数都无法实现函数组合,所以必须把包含外界影响(effectful)副作用不纯代码( ...

  6. 泛函编程(30)-泛函IO:Free Monad-Monad生产线

    在上节我们介绍了Trampoline.它主要是为了解决堆栈溢出(*)错误而设计的.Trampoline类型是一种数据结构,它的设计思路是以heap换stack:对应传统递归算法 ...

  7. 泛函编程(29)-泛函实用结构:Trampoline-不再怕*

    泛函编程方式其中一个特点就是普遍地使用递归算法,而且有些地方还无法避免使用递归算法.比如说flatMap就是一种推进式的递归算法,没了它就无法使用for-comprehension,那么泛函编程也就无 ...

  8. 泛函编程(28)-粗俗浅解:Functor&comma; Applicative&comma; Monad

    经过了一段时间的泛函编程讨论,始终没能实实在在的明确到底泛函编程有什么区别和特点:我是指在现实编程的情况下所谓的泛函编程到底如何特别.我们已经习惯了传统的行令式编程(imperative progra ...

  9. 泛函编程(27)-泛函编程模式-Monad Transformer

    经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative, ...

随机推荐

  1. zmq中zmq&lowbar;poll&lpar;&rpar;函数介绍

    功能: 查看指定的多个socket上哪些socket发生了指定的事件, 事件有: ZMQ_POLLIN: 有消息到来 ZMQ_POLLOUT: 当前无阻塞可以发送消息 ZMQ_POLLERR: 只对标 ...

  2. 在线预览Office文件【效果类似百度文库】(转载)

    转载地址:http://www.cnblogs.com/sword-successful/p/4031823.html 引言 结合上个项目和目前做的这个项目,其中都用到了Office文件在线预览,目前 ...

  3. 记录load data infile 的用法

    load data local infile 'd:/1.txt' into table tcm.wm_dis_category fields terminated by';' lines termi ...

  4. 判断JS是否加载完成

    在正常的加载过程中,js的加载都是同步的,也就是在加载过程中,浏览器会阻塞接下来的内容的加载.这时候我们就要用到动态加载,动态加载是异步的,如果我们在后边要用到这个动态加载的js文件里的东西,就要保证 ...

  5. 【AT91SAM3S】英蓓特EM-SAM3S开发板例子工程中的启动文件分析

    手上一块英倍特的EM-SAM3S开发板,拿到已经有一个月了.本来是做uLoong活动使用的板子,可当初由于不熟悉这个芯片,使用了STM32F4当作了替代.最近准备抽点时间折腾下这个板子. 这个板子的资 ...

  6. WCF开发时如何选择正确的实例模式&lpar;InstanceMode&rpar;?

    WCF开发时如何选择正确的实例模式(InstanceMode)?   在使用WCF实例模型时,你是否思考过这几个的问题: ”WCF中的实例模式如何正确应用”? ”使用WCF中的实例模式有何原则可以遵循 ...

  7. Windows高速定时器&comma;多媒体定时器winmm&period;dll库的使用

    项目里面用到的这些看起来名字高大上的定时器测试下来也是非常不准.看了源码发现也是用System.Timers.Timer或者用的是Thread休眠的方式来实现的.100毫秒就不准了.直到一番搜索,发现 ...

  8. ccf 目录格式转换

    任务背景: 在网络上获取的ccf目录的格式是PDF,但是要进行数据分析时,PDF格式的数据是不符合要求的,因此需要将pdf格式转化为excel格式 任务目的: 将pdf格式的CCF目录转化为excel ...

  9. Machine learning &vert; 机器学习中的范数正则化

    目录 1. \(l_0\)范数和\(l_1\)范数 2. \(l_2\)范数 3. 核范数(nuclear norm) 参考文献 使用正则化有两大目标: 抑制过拟合: 将先验知识融入学习过程,比如稀疏 ...

  10. 在pycharm中运行python程序

    安装PyCharm 安装过程取决于您的操作系统: 在Windows上安装PyCharm 运行.exe您已下载的文件,并按照PyCharm安装向导的说明进行操作. 在macOS上安装PyCharm 打开 ...