泛函编程(28)-粗俗浅解:Functor, Applicative, Monad
经过了一段时间的泛函编程讨论,始终没能实实在在的明确到底泛函编程有什么区别和特点;我是指在现实编程的情况下所谓的泛函编程到底如何特别。我们已经习惯了传统的行令式编程(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的更多相关文章
- 泛函编程(27)-泛函编程模式-Monad Transformer
经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative, ...
- 泛函编程(6)-数据结构-List基础
List是一种最普通的泛函数据结构,比较直观,有良好的示范基础.List就像一个管子,里面可以装载一长条任何类型的东西.如需要对管子里的东西进行处理,则必须在管子内按直线顺序一个一个的来,这符合泛函编 ...
- 泛函编程(26)-泛函数据类型-Monad-Applicative Functor Traversal
前面我们讨论了Applicative.Applicative 就是某种Functor,因为我们可以用map2来实现map,所以Applicative可以map,就是Functor,叫做Applicat ...
- 泛函编程(25)-泛函数据类型-Monad-Applicative
上两期我们讨论了Monad.我们说Monad是个最有概括性(抽象性)的泛函数据类型,它可以覆盖绝大多数数据类型.任何数据类型只要能实现flatMap+unit这组Monad最基本组件函数就可以变成Mo ...
- Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...
- 浅释Functor、Applicative与Monad
引言 转入Scala一段时间以来,理解Functor.Applicative和Monad等概念,一直是我感到头疼的部分.虽然读过<Functors, Applicatives, And Mona ...
- Monad / Functor / Applicative 浅析
前言 Swift 其实比 Objective-C 复杂很多,相对于出生于上世纪 80 年代的 Objective-C 来说,Swift 融入了大量新特性.这也使得我们学习掌握这门语言变得相对来说更加困 ...
- 泛函编程(32)-泛函IO:IO Monad
由于泛函编程非常重视函数组合(function composition),任何带有副作用(side effect)的函数都无法实现函数组合,所以必须把包含外界影响(effectful)副作用不纯代码( ...
- 泛函编程(24)-泛函数据类型-Monad, monadic programming
在上一节我们介绍了Monad.我们知道Monad是一个高度概括的抽象模型.好像创造Monad的目的是为了抽取各种数据类型的共性组件函数汇集成一套组件库从而避免重复编码.这些能对什么是Monad提供一个 ...
随机推荐
- Atitit sql执行计划
Atitit sql执行计划 1.1. 首先要搞明白什么叫执行计划? 执行计划是数据库根据SQL语句和相关表的统计信息作出的一个查询方案,这个方案是由查询优化器自动分析产生的 Oracle中的执行计划 ...
- android上引入七牛 上传图片或者文件 最终整理版本(可用)
前言: 以下是引入七牛的步骤,以及在七牛中上传文件和获取文件private 私密地址. 生成上传凭证和获取下载凭证 具体如果使用过程有什么疑问可以加QQ(备注:七牛问题). 1:导入相关的包(注意是4 ...
- KnockoutJS 3.X API 第四章 数据绑定(3) 控制流if绑定和ifnot绑定
if绑定目的 if绑定一般是格式是data-bind=if:attribute,if后所跟属性或表达式的值应为bool值(也可以是非bool值,当非空字符串时则为真),if绑定的作用与visible绑 ...
- 快速入门系列--WCF--08扩展与新特性
最后一章将进行WCF扩展和新特性的学习,这部分内容有一定深度,有一个基本的了解即可,当需要自定义一个完整的SOA框架时,可以再进行细致的学习和实践. 服务端架构体系的构建主要包含接下来的几个要素:服务 ...
- Sql Server系列:索引维护
1. DBCC SHOWCONTIG 显示指定表的数据和索引的碎片信息.当对表进行大量的修改或添加数据后,执行此语句可以查看有无碎片,显示指定的表或试图的数据和索引的碎片信息. 其语法格式: DBCC ...
- Android 自定义View及其在布局文件中的使用示例(二)
转载请注明出处 http://www.cnblogs.com/crashmaker/p/3530213.html From crash_coder linguowu linguowu0622@gami ...
- CAD2015安装教程 AutoCAD2015中文版安装激活图文教程
28年来,Autodesk一直在坚持不懈地帮助客户提高设计流程效率.AutoCAD提供了可靠的三维自由形状设计工具以及强大的绘图和文档制作功能,在全球拥有数百万用户. autocad2015具有广泛的 ...
- Anliven - 乱炖
001 --- Ping Yourself! 由TCP/IP协议栈而想到的: 你的"协议分层"是如何的?有谁或者什么事务所对应着?谁先谁后,什么重要? 你的"协议栈&qu ...
- Windows Azure Service Bus (2) 队列(Queue)入门
<Windows Azure Platform 系列文章目录> Service Bus 队列(Queue) Service Bus的Queue非常适合分布式应用.当使用Service Bu ...
- Windows Azure Service Bus (5) 主题(Topic) 使用VS2013开发Service Bus Topic
<Windows Azure Platform 系列文章目录> 项目文件,请在这里下载 在笔者之前的文章中Windows Azure Service Bus (1) 基础 介绍了Servi ...