利用php数组函数进行函数式编程
因为一个BUG, 我在一个摇摇欲坠,几乎碰一下就会散架的项目中某一个角落中发现下面这样一段代码
这段程序与那个BUG有密切的关系。 我来回反复的捉摸这段代码, 发现这段代码实现了两个功能
第一个是在一个从数据库中读取的列表数组中找出某个值是最大的一条记录, 并且把这个最大的值和跟这个值相关的时间给取出来。
第二个比较复杂 ,是将这个列表数组中的值映射到另外一个列表数组中, 可以把这个过程看作是SQL中的JOIN操作, 只是JOIN的条件异常复杂 ,在这里我也不详述了,阅读的同学也不必去深入探究。
就这段代码来说, 很难通过大致观察就理解代码的意思 , 代码之中光循环就套了3层, 而且还有多处复杂的条件判断,代码格式混乱,连编码的底线缩进都没有满足。 可悲的是这种类型的代码广泛存在于全球范围内无数Web服务器之上, 每天运行着。
在很久以前, 那会我还很年轻, 看到项目中哪个地方代码有问题,我就难受, 必须改掉它。 后来我发现, 烂代码就像地沟油, 在我所生活的城市, 到哪里都能碰的到, 除非不吃饭, 否则就只能睁一只眼闭一只眼,只要不是味道有问题, 吃也就吃了。
然而,这次却不一样, 这段代码运行在某个功能项的关键部位, 不透彻的理解清晰这段代码, 以后出现问题还是会被卡在这里。虽然现在我理解了这段代码的意思 ,但过些天回过头来, 我又会忘掉这段代码所表达的意义。这并不是我的记忆力问题的, 而是因为这段代码所表达的意途不够清晰。
于是我把代码重构成了下面这个样子, 代码本身的功能并没有变化
是不是还是看不明白代码所表达的意思? 没关系, 因为这段代码所表示的功能太过于复杂 ,而且还依赖于代码所有的整个函数的上下文, 因此无法理解也无可厚非。 但是从代码结构上来看, 重构后的代码的却清晰了不少。
我将原本拥挤在一起的两个功能进行了拆分, 上面部份是求最大值, 下面部份是对两个数组进行映射。 这里我用到了两个PHP中数组的函数 array_map和array_reduce, 这篇文章想表达的主线思路就是利用此类函数来提高PHP代码的可读性。 这类函数主要包括以下4个函数
array_filter
array_map
array_walk
array_reduce
这4个函数威力巨大, 在处理列表数组方面可以完全替换掉for、foreach、while这些循环控制语句, 这也是函数式编程方式在PHP的一部份体现。
1.array_filter函数
这段代码比较好理解,将数组中性别字段为女的数据项提取出来。 整段代码的逻辑大致如下
1.定义result数组, 用来存放结果
2.循环数组, 对每一个数据项进行条件判断, 查看其中的性别字段是否为女
3.如符合条件则放入result数组中
这是原汁原味的命令式程序代码。
如果data变量中的数据并非存放于php数组中, 而是存在于关系数库的表之中, 那何取得性别为女的数据结果呢? 对于程序员来说这貌似是一个更加简单的问题,一句SQL语句就搞定了
显然, 利用SQL查询数据更加方便,意途也更加清晰,毕间一个SQL表达 式就将所有的程序逻辑都给表达了现来。这句SQL只表达了:“我需要性别为女的数据,至于怎么拿, 我不管 ”, 除了结果 , 其它的它一概不知。
我们不妨把这种思路引入到PHP程序设计之中,不也意味着我们的PHP程序的逻辑表达也更加清晰,代码的可读性也更高的。所幸, 这种利用表达式编程的方法在PHP中也完全可以实现。
利用array_filter函数,可以轻松的完成这个任务, 仔细观察一下, 是不是原来的程序逻辑都不见了,包括定义数组、循环、条件判断这些都不见了,逻辑方面是只剩下了一个性别比较语句,这对于代码所实现的功能一目了然。 和上面的SQL比较一下, 这里的性别判断语句就是SQL中where子句后面的条件判断, 而array_filter函数其实就是SQL中的where子句。 这就是SQL语句面向结果编程的逻辑原封不变的在PHP中的体现,也就是时下最流行的“声明性编程”或者也称为“表达式编程”。
此外, 代码中性别判断语句所在的位置称之为lambda表达式, 更通俗一些的叫法是匿名函数。不难看出, 在SQL的where条件中编写条件判断远不如在匿名函数中写PHP代码来的灵活,在where条件中只能执行or和and逻辑,而在php匿名函数中可以随便怎么写,只要函数的返回值是个布尔值就可以了,这也是php声明性编程优于SQL声明性编程的地方。
2.array_map函数
再来看一个例子
数据中的性别字段是中文的,值也是中文的, 现在想把字段名和字段值都改为英文的, 就可以用上面这段代码实现, 至于实现的逻辑这里不赘述了。
下面是利用SQL的实现方式
SQL中case when语句好像不太好看, 但是不影响整体逻辑的表达。 将这段SQL转换成PHP的方式实现
相比之前的PHP实现, 是不是简洁明了了许多。
在这里使用到了 array_map函数 。 在SQL语句中以select语句最为常用, select的字面意思是“选择”,而select语句也被称之为选择查询, 事实上从关系数据库的角度来说,select被称之为“投影”, 并不是查询什么的。 换言之, select 语句只是将SQL的查询结果以一定的方式(选字段、计算值等等)提取出来了。 php中的array_map表达的也是这层意思, “映射”与“投影”完全是一种意思的不同表达。
3.array_walk函数
array_walk函数没有像 array_map和array_filter这样深刻的意义, 但是它在设计可读性良好的代码时也是不可或缺的。
array_walk是for或foreach语句的替代函数
以上代码分别是 foreach和array_walk对于遍历数组的实现方式。 看起来, 好像array_walk的实现方式更加复杂, 但是在更深层次的语义方面
foreach表达的是循环遍历, 但是在这个循环的过程中,要做什么样的处理,是没有任何约束的, 删除被遍历的数组的某一项 ,或者修改一个十万八千里以外的变量的值,这便是所谓的“代码副作用”,俗话说“白蚁虽小, 危害无穷”, 当这些看似微不足道的副作用发展壮大时, 便会给程序员维护程序代码带来的障碍是致命的。
而array_walk函数缺省情况下所有执行代码的作用域都在匿名函数内,如果要依赖或操作函数之外的数据, 必须通过匿名函数的use关键字导入。通俗一点的请, array_walk函数的权限不如foreach来的大, 因此,使用array_walk函数后,虽然无法让你随心所欲的编程,但是大限度的减少了你代码的副作用,两相权衡array_walk所带来的好处还是有值得使用它的理由的。 首先, 大多数时候写代码根本不需要太大的“权限”,其次, 把代码所影响的范围控制到最小好处不言而喻。微信张小龙讲过,微信做的最好的一点便是“克制”,我们写代码又何尝不是。这一点array_filter和array_map中也有体现, 宽泛的讲,所有使用匿名函数的地方都能享受到这个好处。
array_walk所表达的语义就是“假如你需要用到我, 那么你除了遍历以外,其它的事情最好都别干,否则你还是去用原生的foreach吧”
4.array_reduce函数
array_reduce是上面所讲的三个函数的集大成者,这三个函数的底层完全可以由array_reduce实现。
先看一下下面的php代码
常规的PHP写法,代码分别用于计算数组记录中平均年龄和最大年龄,代码需要循环数组,并把计算结果存入一个标量(单个值,区分于列表变量)。
假如要以表达式编程的方式完成编写这两个功能, 利用array_filter、 array_walk、array_map三个函数是很难一部到位的实现的。
于是, 就到了array_reduce大显身手的时候了
上面的代码是求平均年龄和最大年龄的表达式编程的实现,如果对array_reduce函数的工作机制不了解,看上面两段代码会觉得在看天书。
这是 array_reduce函数的实现代码,函数有3个参数, 3个参数的作用分别是
第一个参数$data, 就要是处理的数据源
第二个参数$callback,循环遍历时会被调用的函数,函数返回的结果在下一次循环调用时会被再次当成参数传入。
第三个参数$initial,作为$callback函数被初次调用时的参数传递
再来一个递归版本的array_reduce实现,帮助更好的理解这个函数的使用意义
善用array_reduce函数几乎可以替换掉绝大多数需要使用foreach、for、while语句的代码。
在标准的函数式编程语言中, 是没有循环控制语句的,假如要进循环计算, 都是使用此类函数来实现的, 如果某些极端的情况下这些函数无法满足需求,那么就以手动写递归来实现循环, 以达到表达式编程的目的。
总结一下, 为什么要在写php代码时使用这4个函数
1.通过函数本身的意义就能表达出代码实现了什么样的功能,而不用去琢磨代码具体细节来理解代码的作用
2.表达式编程相对于命令式编程能极大的简化功能的实现过程, 提升编码效率
3.表达式编程对于代码的可读性、可维护性具有非凡的意义
4.利用匿名函数控制代码的副作用
5.由传统的面向过程式程序设计向现代化的函数式编程靠拢
补充:
通过前面示例的讲解, 利用这4个函数实现的代码相对于传统的实现方式并没有不可思议的变化, 然而, 当需要解决的问题复杂到一定程度时, 合理利用这4个函数会使代码的复杂性大规模下降。
利用php数组函数进行函数式编程的更多相关文章
- 转载:利用php数组函数进行函数式编程
因为一个BUG, 我在一个摇摇欲坠,几乎碰一下就会散架的项目中某一个角落中发现下面这样一段代码 这段程序与那个BUG有密切的关系. 我来回反复的捉摸这段代码, 发现这段代码实现了两个功能 第一个是在一 ...
- Python核心编程读笔 10:函数和函数式编程
第11章 函数和函数式编程 一 调用函数 1 关键字参数 def foo(x): foo_suite # presumably does some processing with 'x' 标准调用 ...
- 跟着ALEX 学python day3集合 文件操作 函数和函数式编程 内置函数
声明 : 文档内容学习于 http://www.cnblogs.com/xiaozhiqi/ 一. 集合 集合是一个无序的,不重复的数据组合,主要作用如下 1.去重 把一个列表变成集合 ,就自动去重 ...
- day16_函数作用域_匿名函数_函数式编程_map_reduce_filter_(部分)内置函数
20180729 补充部分代码 20180727 上传代码 #!/usr/bin/env python # -*- coding:utf-8 -*- # ***************** ...
- 函数与函数式编程(生成器 && 列表解析 && map函数 && filter函数)-(四)
在学习python的过程中,无意中看到了函数式编程.在了解的过程中,明白了函数与函数式的区别,函数式编程的几种方式. 函数定义:函数是逻辑结构化和过程化的一种编程方法. 过程定义:过程就是简单特殊没有 ...
- Python之路Python作用域、匿名函数、函数式编程、map函数、filter函数、reduce函数
Python之路Python作用域.匿名函数.函数式编程.map函数.filter函数.reduce函数 一.作用域 return 可以返回任意值例子 def test1(): print(" ...
- python学习7—函数定义、参数、递归、作用域、匿名函数以及函数式编程
python学习7—函数定义.参数.递归.作用域.匿名函数以及函数式编程 1. 函数定义 def test(x) # discription y = 2 * x return y 返回一个值,则返回原 ...
- day03 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数
本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 温故知新 1. 集合 主要作用: 去重 关系测 ...
- python 函数对象(函数式编程 lambda、map、filter、reduce)、闭包(closure)
1.函数对象 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 秉承着一切皆对象的理念,我们再次回头来看函数(function).函 ...
随机推荐
- 初学jQuery之jQuery选择器
今天我们就谈论一下jquery选择器,它们大致分成了四种选择器!!!! 1.基本选择器(标签,ID,类,并集,交集,全局) 1.0(模板) <body> <div id=" ...
- [bzoj1067][SCOI2007]降雨量——线段树+乱搞
题目大意 传送门 题解 我国古代有一句俗话. 骗分出奇迹,乱搞最神奇! 这句话在这道题上得到了鲜明的体现. 我的方法就是魔改版线段树,乱搞搞一下,首先借鉴了黄学长的建树方法,直接用一个节点维护年份的区 ...
- 一种基于路网图层的GPS轨迹优化方案
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 GPS数据正常情况下有20M左右的偏移,在遇到高楼和桥梁等情况 ...
- java 抽象(abstract) 构造 静态(static) 总结--2017-03-02
抽象类:不能实例化!子类继承抽象类, 实例化子类对象才可以调用, 多态的体现; 抽象方法:必须被重写才能被调用; 静态方法:类名直接调用,或者实例化对象调用; 构造方法:new后面的括号里面带参数,就 ...
- 类中的两大类(string类、math类)的应用
类是我们在学习C#的过程中很关键也是特别容易让人蒙逼得地方,类的应用直接可以调用它的属性和方法来进行判断和验证 string类(也叫字符串类) C#中的String类很有用,下面是一些它的常用方法的总 ...
- Android intent 笔记
学习android的intent,将其中的一些总结,整理的笔记记录于此. intent是一个消息传递对象,可以在不同组件间传递数据.Activity,Service,Broadcast Receive ...
- grpc-gateway:grpc对外提供http服务的解决方案
我所在公司的项目是采用基于Restful的微服务架构,随着微服务之间的沟通越来越频繁,就希望可以做成用rpc来做内部的通讯,对外依然用Restful.于是就想到了google的grpc. 使用grpc ...
- 【解题报告】VijosP1448校门外的树(困难版)
原题: 校门外有很多树,有苹果树,香蕉树,有会扔石头的,有可以吃掉补充体力的--如今学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两个操作:K=1,K=1,读入l.r ...
- JavaScript数据结构——栈的实现
栈(stack)是一种运算受限的线性表.栈内的元素只允许通过列表的一端访问,这一端被称为栈顶,相对地,把另一端称为栈底.装羽毛球的盒子是现实中常见的栈例子.栈被称为一种后入先出(LIFO,last-i ...
- Struts2中的配置文件的加载
Struts2框架配置文件加载顺序(服务器启动之后, 这些配置文件会按照顺序一一加载进内存, 进行类等匹配的时候才会去内存查找): 1. default.properties 2. str ...