函数式编程之-bind函数
Bind函数
Bind函数在函数式编程中是如此重要,以至于函数式编程语言会为bind函数设计语法糖。另一个角度Bind函数非常难以理解,几乎很少有人能通过简单的描述说明白bind函数的由来及原理。
这篇文章试图通过“人话”来描述bind函数,并通过浅显的实例为零函数式编程语言的开发者揭秘bind函数的作用及用法。
public string GetSomething(int id)
{
var x = GetFirstThing(id);
if (x != null)
{
var y = GetSecondThing(x);
if(y != null)
{
var z = GetThirdThing(y);
if (z != null)
{
return z;
}
}
}
return null;
}
你一定写过类似的代码,估计你也明白这样的代码看起来很丑陋,一层层的判空嵌套打乱了代码的主题结构。
有没法让他变的更优雅?当然你可以通过"early return"的做法,不过这种方式不在我们的讨论范围之内。
这种风格的代码存在一个明显的code smell, GetFirstThing()/GetSecondThing()/GetThirdThing()等方法有可能返回null,我们说return null是一种不真确的做法,相关分析见拒绝空引用异常。使用Optional类型重构如下:
public Optional<string> GetSomething(int id)
{
var x = GetFirstThing(id);
if (x.HasValue())
{
var y = GetSecondThing(x);
if(y.HasValue())
{
var z = GetThirdThing(y);
if (z.HasValue())
{
return z;
}
}
}
return Optional.None<string>();
}
看起来代码结果跟之前一模一样,重构后的代码并没有变得更漂亮。不过现在的GetFirstThing()/GetSecondThing()/GetThirdThing()方法返回值为Optional<string>类型,不再是普通的string类型:
public Optional<string> GetFirstThing(int id)
{
//...
return Optional.None<string>();
}
重构后的这段代码很有意思,我们可以从函数组合的角度来让整个代码段变的更加优雅。
组合
这段代码其实做了一件事,那就是通过调用三个函数GetFirstThing()/GetSecondThing()/GetThirdThing()来完成一个业务逻辑。从函数式编程思想的角度出发,我们倾向于把若干个小的函数连接起来,根据以前学过的知识,只有一个输入和一个输出的函数才能连接起来:
他们之所以能够连接是因为这两个函数的签名一致,都拥有一个输入和一个输出。
例如:int -> string, string -> bool就可以组合为int -> bool。
而我们此时拥有的三个函数方法签名如下:
GetFirstThing: int -> Optional<string>
GetSecondThing: string -> Optional<string>
GetThirdThing: string -> Optional<string>
显然GetFirstThing和GetSecondThing是无法直接连接的,原因是GetFirstThing返回了Optional<string>类型,而GetSecondThing的输入却是一个普通的string类型。如果我们能够在Optional<T>上扩展一个函数,函数接受一个签名为T -> Optional<T>的函数,那么我们就有可能将上面的三个函数串联起来:
public static class Optional
{
public static Optional<T> Bind<T>(this Optional<T> input, Func<T, Optional<T>> f)
{
if (input.HasValue())
{
return f(input.Value);
}
return Optional.None<T>();
}
}
有了上面这个神奇的bind函数你就可以将上面的三个函数连接起来了:
public string GetSomething(int id)
{
return GetFirstThing(id).Bind(GetSecondThing).Bind(GetThirdThing);
}
用F#实现:
let address = getFirstThing id
|> bind getSecondThing
|> bind getThirdThing
通过bind函数我们成功将三个函数连接了起来, 同时将判空放在了bind函数里,从而保持主要逻辑部分更加线性和清晰。
如何编写属于自己的bind函数
- 首先需要定义一个泛型类型E<a>,例如我们上面例子中提到的Optional<T>
- 编写属于Optional<T>的bind函数,bind函数的签名为E<a> -> (f: a -> E<b>) -> E<b>。 接收一个E<a>,同时接受一个签名为a -> E<b>的函数,返回E<b>。
List<T>中的bind函数
我们经常用的List<T>就是一个典型的泛型类型,那他上面有没有bind函数?当然有,不过叫做SelectMany, Scala中也叫flatMap。
看一下SelectMany的方法签名,正好符合bind函数的签名要求:
public static IEnumerable<TResult> SelectMany<TSource,
TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
{
//...
}
SelectMany可以用在什么样的场景中?
例如有这样一个场景,一篇文章(paper)可以有若干章节(section)组成,每个章节(section)又有若干行(row)组成,每行(row)有若干单词(word)组成。
问:给定一篇文章(paper),请找出大于10行(row)的章节(section),里面排除注释的行(row)总共的单词(word)数量。
首先根据需求变下下面的若干函数:
private List<Paper.Section> GetSections(Paper paper)
{
return paper.Sections.Where(s => s.Rows.Count > 10).ToList();
}
private List<Paper.Section.Row> GetRows(Paper.Section section)
{
return section.Rows.Where(r=>!r.IsComment).ToList();
}
private List<Paper.Section.Row.Word> GetWords(Paper.Section.Row row)
{
return row.Words;
}
且看这三个函数的签名:
GetSections: Papaer -> List<Section>
GetRows: Section -> List<Row>
GetWords: Row -> List<Word>
正好这就是就符合bind函数连接的需求:
var length = GetSections(paper)
.SelectMany(GetRows)
.SelectMany(GetWords)
.Count();
F#实现:
let words = getSections paper
|> bind getRows
|> bind getWords
words.Length
bind函数的语法糖支持
bind函数在函数式编程中如此常见,以至于需要设计单独的语法糖,Haskell中叫do natation, Scala中叫for comprehension,F#用Computation expressions:
list {
let! section = getSections(paper)
let! row = getRows(section)
let! word = getWord(row)
return word
}
函数式编程之-bind函数的更多相关文章
- 【Python】[函数式编程]高阶函数,返回函数,装饰器,偏函数
函数式编程高阶函数 就是把函数作为参数的函数,这种抽象的编程方式就是函数式编程.--- - -跳过,不是很理解,汗 - ---
- 从函数式编程到Ramda函数库(二)
Ramda 基本的数据结构都是原生 JavaScript 对象,我们常用的集合是 JavaScript 的数组.Ramda 还保留了许多其他原生 JavaScript 特性,例如,函数是具有属性的对象 ...
- 从函数式编程到Ramda函数库(一)
函数式编程是种编程方式,它将电脑运算视为函数的计算.函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值).和指令式编程相比, ...
- Python之路(第七篇)Python作用域、匿名函数、函数式编程、map函数、filter函数、reduce函数
一.作用域 return 可以返回任意值例子 def test1(): print("test1") def test(): print("test") ret ...
- python函数式编程之返回函数、匿名函数、装饰器、偏函数学习
python函数式编程之返回函数 高阶函数处理可以接受函数作为参数外,还可以把函数作为结果值返回. 函数作为返回值 def laxy_sum(*args): def sum(): ax = 0; fo ...
- Python之路Python作用域、匿名函数、函数式编程、map函数、filter函数、reduce函数
Python之路Python作用域.匿名函数.函数式编程.map函数.filter函数.reduce函数 一.作用域 return 可以返回任意值例子 def test1(): print(" ...
- Learning Python 012 函数式编程 2 返回函数 匿名函数 装饰器 偏函数
Python 函数式编程 2 返回函数 返回函数的意思就是:函数作为返回值.(高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回.) 举个例子:实现一个可变参数的求和. 正常的函数: de ...
- 跟着ALEX 学python day3集合 文件操作 函数和函数式编程 内置函数
声明 : 文档内容学习于 http://www.cnblogs.com/xiaozhiqi/ 一. 集合 集合是一个无序的,不重复的数据组合,主要作用如下 1.去重 把一个列表变成集合 ,就自动去重 ...
- 理解函数式编程中的函数组合--Monoids(二)
使用函数式语言来建立领域模型--类型组合 理解函数式编程语言中的组合--前言(一) 理解函数式编程中的函数组合--Monoids(二) 继上篇文章引出<范畴论>之后,我准备通过几篇文章,来 ...
- JavaScript函数式编程(纯函数、柯里化以及组合函数)
JavaScript函数式编程(纯函数.柯里化以及组合函数) 前言 函数式编程(Functional Programming),又称为泛函编程,是一种编程范式.早在很久以前就提出了函数式编程这个概念了 ...
随机推荐
- 【C语言编程练习】5.12 兔子产仔问题
1. 题目要求 把一对新生的兔子关在屋子里,已知一对两个月大的兔子以后买一个月可以生一对小兔子,而这一对心生的小兔子初生两个月以后才可以生小兔子,假如一年内没有死亡,一年可以繁殖成多少对? 2. 题目 ...
- CentOS下SVN服务的启动与关闭
CentOS下SVN服务的启动与关闭 操作系统:CentOS 6.5 SVN版本:1.8.11 启动SVN服务: svnserve -d -r /home/svn /home/svn 为版本库的根 ...
- 左倾堆C++实现
#include <iostream> #include <vector> #include <queue> using namespace std; templa ...
- Golang的模块管理Module
Golang 1.11版本终于支持了官方的模块依赖管理功能,1.11以前想要实现依赖管理只能够通过借助第三方库来实现,1.11以前的版本Golang项目必须依赖以GOPATH,从当前版本开始Golan ...
- Java EE 导图
- MySql操作(一)
1.连接Mysql 格式: mysql -h主机地址 -u用户名 -p用户密码 1.连接到本机上的MYSQL.首先打开DOS窗口,然后进入目录mysql\bin,再键入命令mysql -u root ...
- IDEA环境下SSM整合------注解开发
根据前一篇文章的步骤,目前项目进度应该是:核心过滤器配置完成.DispatcherServlet和ContextLoader配置完成.数据库dataSource配置完成.视图解析器配置完成.Mappe ...
- UI自动化项目搭建(selenium+testng+java+maven)
一 工具准备 selenium chormDriver 二 环境准备 三搭建步骤
- Where is the Marble? (寻找大理石上的数字)
(先上题目) (题目描述)Raju and Meena love to play with Marbles. They have got a lot of marbles with numbers w ...
- truffle 开发入门教程
1.安装nodejs 2.安装truffle:执行命令: npm install -g truffle 3.truffle init (可加项目名) 4.windows 要删除根目录中的 truff ...