经过了一段时间的泛函编程讨论,始终没能实实在在的明确到底泛函编程有什么区别和特点;我是指在现实编程的情况下所谓的泛函编程到底如何特别。我们已经习惯了传统的行令式编程(imperative programming),总是先入为主的认为软件编程就是一行接着一行的更改某些变量状态指令:明刀明枪,字里行间目的和方式都很明确。我们就以一步步更改程序状态的方式,一行一行的拼凑指令:这就是典型的行令式编程了。

泛函编程,顾名思义,就是用一个个函数来编程。讲的再深入点就是通过函数组合来更改程序状态。什么意思?为什么?

严格来讲,在泛函编程中是没有在某个地方申明一个变量,然后在一些函数里更新这个变量这种方式的。与申明变量相对应的是泛函编程会把所谓变量嵌入在一个结构里,如:F[A]。F是某种高阶类型,A就是那个变量。如果我们需要去更改这个变量A就必须设计一套专门的函数来做这件事了。从某些方面这也解释了何谓泛函编程。我用粗俗的语言来描述这两种编程模式的区别:行令编程就像在床面上打扑克,而泛函编程就好比在被窝里打牌。两种操作一样都是打牌,只是打牌的环境不同。实际上泛函编程的这种在套子内部更新变量的方式恰恰是我们选择泛函模式的考虑重点:它可以使程序运行更安全稳定、能轻松解决很多行令编程模式中存在的难题,这些优点将会在将来的应用中逐渐显现出来。

既然变量封装在了套子里面,那么自然需要设计一些在套子里更新变量的函数了:

我们的目的是用某些函数把F[A]变成F[B]:A 变成了 B,但任然封装在 F[] 里:

下面我们列出几个函数,它们的操作结果都是一样的:

A => B      >>> F[A] => F[B]

A => F[B]   >>> F[A] => F[B]

F[A => B]   >>> F[A] => F[B]

就是说我们有这三款函数,问题是应该怎么把F[A]变成F[B]。我们先定义一个测试用的数据类型:

case class Box[A](a: A)  >>> 这是一个带嵌入变量的泛函类型

下面我们就试着实现这三款函数:

1、 A => B

   case class Box[A](a: A)
def map[A,B](f: A => B): Box[A] => Box[B] = {
(ba: Box[A]) => Box(f(ba.a))
} //> map: [A, B](f: A => B)ch12.ex3.Box[A] => ch12.ex3.Box[B]

我们就试着用这个map来更新a:

 //f: String => Int
def lengthOf(s: String): Int = s.length //> lengthOf: (s: String)Int
val funcMapTransform = map(lengthOf) //> funcMapTransform : ch12.ex3.Box[String] => ch12.ex3.Box[Int] = <function1>
funcMapTransform(Box("Hello World!")) //> res0: ch12.ex3.Box[Int] = Box(12)

恭喜!我们成功设计了个Functor函数

2、A => F[B]

   case class Box[A](a: A)
def flatMap[A,B](f: A => Box[B]): Box[A] => Box[B] = {
(ba: Box[A]) => f(ba.a)
}

我们用flatMap进行Box[A] => Box[B]:

 //f: String => Box[Int]
def boxedLengthOf(s: String) = Box(s.length) //> boxedLengthOf: (s: String)ch12.ex3.Box[Int]
val funcFlatMapTransform = flatMap(boxedLengthOf)
//> funcFlatMapTransform : ch12.ex3.Box[String] => ch12.ex3.Box[Int] = <functio
//| n1>
funcFlatMapTransform(Box("Hello World!")) //> res1: ch12.ex3.Box[Int] = Box(12)

这个flatMap就是个Monad函数

3、Box[A => B]

   case class Box[A](a: A)
def apply[A,B](f: Box[A => B]): Box[A] => Box[B] = {
(ba: Box[A]) => Box(f.a(ba.a))
}

我们可以使用一下这个apply函数:

  //f: Box[String => Int]
val funcApplyTransform = apply(Box(lengthOf _))//> funcApplyTransform : ch12.ex3.Box[String] => ch12.ex3.Box[Int] = <function1
//| >
funcApplyTransform(Box("Hello World!")) //> res2: ch12.ex3.Box[Int] = Box(12)

apply函数就是Applicative函数

虽然我们在上面分别实现了Functor,Applicative,Monad的功能函数。但Functor,Applicative,Monad都是泛函数据类型,我们还没有明确定义这些数据类型。这些数据类型自提供了操作函数对嵌在内部的变量进行更新。也就是说它们应该自带操作函数。

我们再来看看以上的函数款式:

(A => B) => (F[A] => F[B])

(A => B) => F[A] => F[B]

uncurry ((A => B), F[A]) => F[B]

map(F[A])(A => B): F[B]

flatMap(F[A])(A => F[B]): F[B]

apply(F[A])(F[A => B]): F[B]

现在所有函数都针对共同的数据类型F[A]。它们已经具备了数据类型内嵌函数的特性。

下面我们再用规范方式定义F[A]这个数据类型。我们采用trait,因为继承方式会更灵活:

   trait Box[A] {
def get: A
}
object Box {
def apply[A](a: A) = new Box[A] {
def get = a
}
}

用get来获取嵌入结构的变量值。apply是Box类型的创造工厂函数。现在我们可以创建Box实例:

   val bxHello = Box("Hello")                      //> bxHello  : ch12.ex4.Box[String] = ch12.ex4$Box$$anon$1@3581c5f3
bxHello.get //> res0: String = Hello

如果Box是Functor,就必须实现map函数:

   trait Box[A] {
def get: A
def map[B](f: A => B): Box[B] = Box(f(get))
}
object Box {
def apply[A](a: A) = new Box[A] {
def get = a
}
}
val bxHello = Box("Hello") //> bxHello : ch12.ex4.Box[String] = ch12.ex4$Box$$anon$1@3581c5f3
bxHello map {_.length} //> res0: ch12.ex4.Box[Int] = ch12.ex4$Box$$anon$1@340f438e
(bxHello map {a => a.length}).get //> res1: Int = 5

现在Box是个Functor,bxHello是个Functor实例。

   trait Box[A] {
def get: A
def map[B](f: A => B): Box[B] = Box(f(get))
def flatMap[B](f: A => Box[B]): Box[B] = f(get)
def apply[B](f: Box[A => B]): Box[B] = Box(f.get(get))
}
object Box {
def apply[A](a: A) = new Box[A] {
def get = a
}
}
val bxHello = Box("Hello") //> bxHello : ch12.ex4.Box[String] = ch12.ex4$Box$$anon$1@3581c5f3
bxHello map {_.length} //> res0: ch12.ex4.Box[Int] = ch12.ex4$Box$$anon$1@340f438e
(bxHello map {a => a.length}).get //> res1: Int = 5 bxHello flatMap {a => Box(a.length)} //> res2: ch12.ex4.Box[Int] = ch12.ex4$Box$$anon$1@30c7da1e
(bxHello flatMap {a => Box(a.length)}).get //> res3: Int = 5 def lengthOf(s: String): Int = s.length //> lengthOf: (s: String)Int
bxHello apply {Box(lengthOf _)} //> res4: ch12.ex4.Box[Int] = ch12.ex4$Box$$anon$1@5b464ce8
(bxHello apply {Box(lengthOf _)}).get //> res5: Int = 5

实现了flatMap, apply后Box是Functor,Applicative同时还是Monad

值得关注的是Monad特性。有了Monad特性我们可以在for-comprehension这个封闭的环境里进行行令编程

   val word = for {
x <- Box("Hello")
y = x.length
z <- Box(" World!")
w = x + z
} yield w //> word : ch12.ex4.Box[String] = ch12.ex4$Box$$anon$1@2d554825
word.get //> res6: String = Hello World!

注意:在for-comprehension这个环境里,运算对象x,y,z,w都是脱了衣服的基础类型。这样我们才能采用熟悉的编程方式工作。

乘这个机会再示范另外一种实现方式:

  trait Box[A] {
def get: A
}
object Box {
def apply[A](a: A) = new Box[A] {
def get = a
}
}
class BoxOps[A](ba: Box[A]) {
def map[B](f: A => B): Box[B] = Box(f(ba.get))
def flatMap[B](f: A => Box[B]): Box[B] = f(ba.get)
def apply[B](f: Box[A => B]): Box[B] = Box(f.get(ba.get))
}
implicit def toBoxOps[A](ba: Box[A]) = new BoxOps(ba)
//> toBoxOps: [A](ba: ch12.ex5.Box[A])ch12.ex5.BoxOps[A] val bxHello = Box("Hello") //> bxHello : ch12.ex5.Box[String] = ch12.ex5$Box$$anon$1@511baa65
bxHello map {_.length} //> res0: ch12.ex5.Box[Int] = ch12.ex5$Box$$anon$1@340f438e
(bxHello map {a => a.length}).get //> res1: Int = 5 bxHello flatMap {a => Box(a.length)} //> res2: ch12.ex5.Box[Int] = ch12.ex5$Box$$anon$1@30c7da1e
(bxHello flatMap {a => Box(a.length)}).get //> res3: Int = 5 def lengthOf(s: String): Int = s.length //> lengthOf: (s: String)Int
bxHello apply {Box(lengthOf _)} //> res4: ch12.ex5.Box[Int] = ch12.ex5$Box$$anon$1@5b464ce8
(bxHello apply {Box(lengthOf _)}).get //> res5: Int = 5 val word = for {
x <- Box("Hello")
y = x.length
z <- Box(" World!")
w = x + z
} yield w //> word : ch12.ex5.Box[String] = ch12.ex5$Box$$anon$1@2d554825
word.get //> res6: String = Hello World!

以上方式得到同样的数据类型效果。同时又能更好的对源代码进行分类组织,是规范的泛函组件库编码方式。

看来,Functor, Applicative, Monad除了名称怪异外实际上并不可怕,我们可以从它们的用途中了解它们的意义。

泛函编程(28)-粗俗浅解:Functor, Applicative, Monad的更多相关文章

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

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

  2. 泛函编程(6)-数据结构-List基础

    List是一种最普通的泛函数据结构,比较直观,有良好的示范基础.List就像一个管子,里面可以装载一长条任何类型的东西.如需要对管子里的东西进行处理,则必须在管子内按直线顺序一个一个的来,这符合泛函编 ...

  3. 泛函编程(26)-泛函数据类型-Monad-Applicative Functor Traversal

    前面我们讨论了Applicative.Applicative 就是某种Functor,因为我们可以用map2来实现map,所以Applicative可以map,就是Functor,叫做Applicat ...

  4. 泛函编程(25)-泛函数据类型-Monad-Applicative

    上两期我们讨论了Monad.我们说Monad是个最有概括性(抽象性)的泛函数据类型,它可以覆盖绝大多数数据类型.任何数据类型只要能实现flatMap+unit这组Monad最基本组件函数就可以变成Mo ...

  5. Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理

    Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...

  6. 浅释Functor、Applicative与Monad

    引言 转入Scala一段时间以来,理解Functor.Applicative和Monad等概念,一直是我感到头疼的部分.虽然读过<Functors, Applicatives, And Mona ...

  7. Monad / Functor / Applicative 浅析

    前言 Swift 其实比 Objective-C 复杂很多,相对于出生于上世纪 80 年代的 Objective-C 来说,Swift 融入了大量新特性.这也使得我们学习掌握这门语言变得相对来说更加困 ...

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

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

  9. 泛函编程(24)-泛函数据类型-Monad, monadic programming

    在上一节我们介绍了Monad.我们知道Monad是一个高度概括的抽象模型.好像创造Monad的目的是为了抽取各种数据类型的共性组件函数汇集成一套组件库从而避免重复编码.这些能对什么是Monad提供一个 ...

随机推荐

  1. lua跨平台文件夹遍历匹配查找

    require"lfs" --[[Desc:在B路径D文件中下 搜寻A路径下的没用到的C类文件: 并且将没用到的B类文件名称打印出来: 设置好路径拖到lua自带编辑器中即可运行之; ...

  2. salesforce 零基础学习(十六)Validation Rules & Date/time

    上一篇介绍的内容为Formula,其中的Date/time部分未指出,此篇主要介绍Date/time部分以及Validation rules. 本篇参考PDF: Date/time:https://r ...

  3. java 类之间的关系

    一.继承 二.实现 三.组合

  4. 每天一个linux命令(38):cal 命令

    cal命令可以用来显示公历(阳历)日历.公历是现在国际通用的历法,又称格列历,通称阳历."阳历"又名"太阳历",系以地球绕行太阳一周为一年,为西方各国所通用,故 ...

  5. javascript_core_08之闭包、对象、原型

    1.闭包: ①外层函数包裹受保护的变量和操作变量的内层函数: ②外层函数将内层函数返回到外部: ③调用外部函数,获得内层函数的对象: 2.面向对象:用对象描述现实一个具体事物属性和功能,按需调用功能, ...

  6. Orlion个人博客 | 全栈工程师之路-www.orlion.ga

    自己用wordpress+bootstrap搭建了一个博客,网址http://www.orlion.ga,用来记录博主学习和成长,主要关注JAVA.LANMP.前端与客户端(Android).算法与数 ...

  7. 编译原理:正规式转变成DFA算法

    //将正规式转变成NFApackage hjzgg.formal_ceremony_to_dfa; import java.util.ArrayList; class Edge{ public int ...

  8. 牛刀小试:使用Reactive Extensions(Rx),对短时间内多次发生的事件限流

    我之前有一篇文章介绍到了Reactive Extension这个组件,请参考下面的文章,其中有一些基本的概念和相关的链接 牛刀小试:使用Reactive Extensions(Rx),一行代码实现多线 ...

  9. prototype.js 和 jQuery.js中 ajax 的使用

    这次还是prototype.js 和 jQuery.js冲突的问题,前面说到过解决办法http://www.cnblogs.com/Joanna-Yan/p/4836252.html,以及上网说的大部 ...

  10. Android基于mAppWidget实现手绘地图(十五)–如何控制放大缩小

    一般来说,可以使用以下几种方式来控制地图的放大/缩小 : 1. 使用控件底部的缩放按钮 2.双击控件 3.pinch手势 4.物理按键 :I键标识缩小  :O键表示放大.(只有设备具有物理按键才行) ...