PHP高级编程-回归原生态-函数式编程与数组
moboyou 2025-06-25 17:20 3 浏览
4.2.4 函数式编程与数组
在函数式编程的世界里,针对集合的操作有三大类,分别是:映射、过滤和归约。
虽然PHP是一门解释性脚本语言,并且支持面向过程编程和面向对象编程,但与函数式编程还是有很大区别的。PHP也为映射、过滤和归约提供了对应的函数。它们分别是:
- 映射:array_map()
- 过滤:array_filter()
- 归纳:array_reduce()
下面可以通过一个简单的例子来快速认识这三大类的操作。假设我们有1、2、3、4、……、9、10这样十个数字,作为最初的集合元素。则有:
<?php
$data = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
如果我们想让每个数字都翻N倍,即对原来每个元素,通过一个规则映射到一个新的集合,那么这就是映射。使用array_map()函数可轻松实现这一点。
// 集合的映射操作// 假设翻2倍
$n = 2;
$dataDouble = array_map(function ($value) use ($n) {
return $value * $n;}, $data);
print_r($dataDouble);
通过映射后,新集合与旧集合的元素是一一对应的,数量不会少也不会多。这就和投影一样。这里的执行结果会输出:
Array(
[0] => 2
[1] => 4
[2] => 6
[3] => 8
[4] => 10
[5] => 12
[6] => 14
[7] => 16
[8] => 18
[9] => 20)
而过滤操作,显然会对集合的元素数量有影响,通常会根据条件过滤移除部分元素,所以执行过滤后,元素个数会减少。继续前面的示例,如果要找出全部的奇数,可以:
// 集合的过滤操作
$dataOdd = array_filter($data, function ($value) {
return ($value % 2) == 1;});
print_r($dataOdd);
输出结果是:
Array(
[0] => 1
[2] => 3
[4] => 5
[6] => 7
[8] => 9)
最后,是集合的归约操作,即把全部的元素通过某个规则全部合成一个新东西。注意,映射和过滤后的新元素的类型,和原来元素的类型是一样的。即原来是整型的元素,操作后还会是整型的元素,虽然数量上会有所变化。而归纳则是完全不同的操作,除了最后元素个数变成只有一个外,新元素的类型极大可能会是新的类型。
拿这里的例子来说,假设我们要把全部的数字按字符串连接起来,那么最后就会形成一个字符串:12345678910。使用array_reduce()的实现代码是:
// 集合的归约操作
$str = array_reduce($data, function ($carry, $item) {
return $carry . $item;}, '');
echo $str, PHP_EOL;
在函数式编程的世界里,函数是“一等公民”。而在OOP世界里,类才是“一等公民”。在最开始时,我们需要对原来的数值翻N倍,这个N倍暂时是通过参数来传递的。又如,在过滤奇数时,是直接硬编程在代码里的,如果我要改成过滤偶数呢,怎么办?又假设我会动态传入过滤条件呢,又怎么办?大家可以回想一下,在面向过程编程和面向对象编程里,我们都是怎样解决类似这样的需求的。
这里简单分享一下,在函数式编程世界里巧妙的做法。因为函数是它们的“一等公民”,函数可以作为函数的参数也可以作为函数的返回值,甚至还可以作为变量存储起来,这叫做高阶函数。通过原子的操作,再结合部分施用或柯里化,就能灵活组合成复杂的功能。和面向对象编程世界的自顶而下的封装不同,函数式编程则是自底而上的组合形式。
初次接触函数式编程的同学,对于柯里化这个概念有点陌生。这里稍微解释一下,简单地理解,可以看成一开始某个函数需要两个参数的,但我先提供一个参数,然后返回一个半成品函数。剩下的另外一个参数,再作为参数传给这个新的函数。打个比方,就类似分期付款一样,一开始钱不够,先给部分,后面再分期支付。以翻N倍为例,这里有两个参数,一个是原始的数值,另一个参数是翻多少倍。但再一推导,实际上就是两个数相乘。
先睹为快,使用Scala函数式编程语言实现上面同样功能的完整代码。将下面Scala代码保存到ArrayCurry.scala文件,位置目录任意。
/**
* 使用Scala的同等实现
*/object ArrayCurry {
def main(args: Array[String]): Unit = {
val data: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 集合的映射操作
val dataDouble = data.map(x => x * 2);
println(dataDouble.mkString(" "))
// 集合的过滤操作
val dataOdd = data.filter(_ % 2 == 1)
println(dataOdd.mkString(" "))
// 集合的归约操作
val str: String = data.foldLeft("")((x, y) => x.toString() + y.toString())
println(str)
}
在安装了Scala的情况下,可以执行以下命令:
$ scalac ./ArrayCurry.scala && scala ArrayCurry
然后就可能看到输出:
2 4 6 8 10 12 14 16 18 201 3 5 7 912345678910
除了输出格式外,效果和前面是一样的。但Scala的代码更简洁、更优雅。
再来稍后看一下柯里化的应用。先添加一个柯里化实现的类成员函数:
object ArrayCurry {
// 柯里化
def curry[A, B, C](f: (A, B) => C) : A => (B => C) = {
(a: A) => ((b: B) => f(a, b))
}}
然后在main()函数最后追加以下代码,就可以应用柯里化实现对不同倍数的相乘。
object ArrayCurry {
def main(args: Array[String]): Unit = {
// ……
// 两数相乘
def multi(left: Int, right: Int): Int = {
left * right
}
val multi2 = curry(multi)(2)
println(data.map(multi2).mkString(" "))
}
从这里可以窥探到函数式编程的思想。即找出原子性的操作,再基于此基础推导出更多衍生的操作。例如这里的,两数相乘multi是最基本的操作,其次再到翻2倍的multi2,当然基于multi我们可以快速创建翻3倍、翻10倍、翻N倍的函数——只需要传一个参数即可。最后,把这个翻2倍的因子传递给映射操作,就可以完成对整个集合的映射操作。多么微妙!
至此,我们简单回顾一下。一开始,我们学习了PHP数组关于映射、过滤和归约的三大类操作。然后使用函数式编程语言Scala同样实现了一遍,还稍微领略了函数式编程的魅力和思想。但这本书更多是关于PHP的书,这里要告诫各位开发同学的是,虽然函数式编程的魅力很大,但PHP语言更多是简单、实用、方便。如果没有必要,建议不要采用array_map()、array_filter()、array_reduce()这些函数。
有同学开始感到困惑了,讲了那么多,最后是为了让我们不要使用?为什么呢?因为匿名函数会增加代码的复杂度,增加代码的嵌套层级,不容易理解,更不容易维护。现在的时代不再是以前写出别人看不懂的代码就是高手,而是如何写出连实习生都能快速理解的代码才是专家。毕竟PHP没有Scala那么强悍的语法糖,也没有像Javascript的上下文,过度使用这类数组的函数,反而会适得其反。
最后,我们来看下,PHP回归到简单版的实现方式。其实很简单,改用foreach进行循环处理即可。不需要花俏、华而不实的外表。以下代码,想必连刚学PHP的同学也能快速理解,并且维护起来成本更低。
// 翻2倍
$dataDouble = array();
$n = 2;foreach ($data as $it) {
$dataDouble[] = $it * $n;}
print_r($dataDouble);// 取奇数
$dataOdd = array();
foreach ($data as $it) {
if ($it % 2 == 1) {
$dataOdd[] = $it;
}}
print_r($dataOdd);// 字符串拼接
$str = '';
foreach ($data as $it) {
$str .= $it;}
echo $str, PHP_EOL;
如果非得要用一句话来总结,那就是:不要让事情变得更复杂,不要增加人为的偶然复杂性。
4.2.5 数组类
前面介绍的关于数组的排序、集合的三大操作,都是使用函数的,是面向过程的。接下来,了解一下面向对象编程相关的知识。数组类是什么意思呢?不难理解,数组类就是具体数组特性的类。
对于一个数组,可以获取和修改某个键的值,也可以进行删除、判断键是否存在。如果一个类想实现具有数组的访问特性,就需要实现ArrayAccess(数组式访问)接口,并且需要实现以下四个方法:
- ArrayAccess::offsetExists — 检查一个偏移位置是否存在
- ArrayAccess::offsetGet — 获取一个偏移位置的值
- ArrayAccess::offsetSet — 设置一个偏移位置的值
- ArrayAccess::offsetUnset — 复位一个偏移位置的值
结合下面示例,可以看更好地理解这四个方法的用处。
$arr = array('dogstar' => 95, 'aevit' => 98);// 检查一个偏移位置是否存在
var_dump(isset($arr['dogstar']));// 获取一个偏移位置的值
var_dump($arr['dogstar']);// 设置一个偏移位置的值
$arr['dogstar'] = 96;// 复位一个偏移位置的值
unset($arr['dogstar']);
那什么时候会用到数组类呢?这里先简单抛个砖,后面会继续深入讨论。在PHP开源框架或开源类库里,会用到这个ArrayAccess接口实现一些高级的功能操作。以流行的Phalcon框架为例,它的DI容器,以及它的依赖注入都是设计得非常巧妙且强大的。其中就可以通过数组的方式来访问DI容器。例如:
<?php
$di = new Phalcon\DI();// 通过数组方式设置
$di["request"] = new \Phalcon\Http\Request();// 通过数组方式读取
var_dump($di["request"]);
PhalApi开源接口框架受此启发,也引入了DI依赖注入,同时也实现了ArrayAccess接口。在PhalApi的PHP源代码里,我们可以找到对应的实现代码。
<?php
namespace PhalApi;
class DependenceInjection implements ArrayAccess {
// ……
/** ------------------ ArrayAccess(数组式访问)接口 ------------------ **/
public function offsetSet($offset, $value) {
$this->set($offset, $value);
}
public function offsetGet($offset) {
return $this->get($offset, NULL);
}
public function offsetUnset($offset) {
unset($this->data[$offset]);
}
public function offsetExists($offset) {
return isset($this->data[$offset]);
}
}
当开发工程师需要使用DI容器时,就可以像Phalcon那样,使用数组的方式来操作。例如和前面类似的实现:
<?php
// 通过快速函数来获取
$di = PhalApi\DI();// 通过数组方式设置
$di["request"] = new \PhalApi\Request();// 通过数组方式读取
var_dump($di["request"]);
这里关于数组的访问接口,以及DI容器的介绍,先到这里。后面讲魔术方法时,我们再来一起深入探讨。
相关推荐
- php宝塔搭建免登录积分商城系统php源码
-
大家好啊,欢迎来到web测评。本期给大家带来一套php开发的免登录积分商城系统php源码,上次是谁要的系统项目啊,帮你找到了,还说不会搭建,让我帮忙录制一期教程,趁着今天有空,简单的录制测试了一下,部...
- 宝塔docker搭建一键部署DooTask开源任务管理工具php源码pro版
-
大家好啊,我是测评君,欢迎来到web测评,本期给大家分享一下怎么在宝塔使用docker管理器一键部署DooTask开源项目任务管理工具源码(pro版),这是一个朋友分享推荐的,让我录制一期宝塔搭建教程...
- 在线考试教学系统平台源码/视频教学系统/在线考试系统PHP源码
-
在线考试教学系统平台系统源码,视频教学系统PHP源码,在线考试系统PHP源码。安装说明:1、部署好网站环境:php5.6+mysql2、将源码传至网站根目录,php源码用二进制上传,或者上传压缩包在空...
- php宝塔搭建实战宏仔杂货商城小店系统php源码
-
大家好啊,欢迎来到web测评。本期给大家带来一套php开发的宏仔杂货商城小店系统源码,上次是谁要的系统项目啊,帮你找到了,还说不会搭建,让我帮忙录制一期教程,趁着今天有空,简单的录制测试了一下,部署方...
- 分析和解析PHP代码的7大工具
-
PHP已成为时下最热门的编程语言之一,然而却有许多PHP程序员苦恼找不到合适的工具来帮助自己分析和解析PHP代码。今天小编就为大家介绍几个非常不错的工具,来帮助程序员们提高自己的工作效率,一起来看看吧...
- php宝塔搭建部署小闪三消游戏PHP源码带后台
-
大家好啊,欢迎来到web测评。本期给大家带来一套php开发的小闪三消游戏PHP源码带后台,上次是谁要的系统项目啊,帮你找到了,还说不会搭建,让我帮忙录制一期教程,趁着今天有空,简单的录制测试了一下,部...
- php宝塔搭建部署昼夜双色WordPress导航模板php源码
-
大家好啊,欢迎来到web测评。本期给大家带来一套php开发的昼夜双色WordPress导航模板php源码,上次是谁要的系统项目啊,帮你找到了,还说不会搭建,让我帮忙录制一期教程,趁着今天有空,简单的录...
- php宝塔搭建实战可乐实物微商城系统php源码
-
大家好啊,欢迎来到web测评。本期给大家带来一套php开发的可乐实物微商城系统php源码php源码,上次是谁要的系统项目啊,帮你找到了,还说不会搭建,让我帮忙录制一期教程,趁着今天有空,简单的录制测试...
- php宝塔搭建实战wordpress英文国外资讯整站php源码
-
大家好啊,欢迎来到web测评。本期给大家带来一套php开发的国外财经资讯商业新闻整站php源码,具有全自动采集发布广告联盟网站的特性,是wordpress二次开发的。上次是谁要的系统项目啊,帮你找到了...
- php宝塔搭建实战小霸王站群池管理系统php源码
-
大家好啊,欢迎来到web测评。本期给大家带来一套php开发小霸王站群池管理系统php源码,上次是谁要的系统项目啊,帮你找到了,还说不会搭建,让我帮忙录制一期教程,趁着今天有空,简单的录制测试了一下,部...
- 完美可用的全媒体广告精准营销服务平台PHP源码
-
今天测试了一套php开发的企业网站展示平台,还是非常不错的,下面来给大家说一下这套系统。1、系统架构这是一套基于ThinkPHP框架开发的HTML5响应式全媒体广告精准营销服务平台PHP源码。现在基于...
- php宝塔搭建实战红蓝两款导航网站自动收录审核检测友链php源码
-
大家好啊,欢迎来到web测评。本期给大家带来一套php开发的php宝塔搭建实战红蓝两款导航网站自动收录审核检测友链php源码。上次是谁要的系统项目啊,帮你找到了,还说不会搭建,让我帮忙录制一期教程,趁...
- php宝塔搭建部署实战抽奖系统开源php源码
-
大家好啊,我是测评君,欢迎来到web测评。本期给大家带来一套抽奖系统开源php源码。感兴趣的朋友可以自行下载学习。技术架构PHP5.4+nginx+mysql5.7+JS+CSS+...
- PHP餐饮点餐外卖系统源码/H5在线点餐系统/支持多餐桌生成
-
搭建一条龙包服务器域名168元安装教程1.获取到项目源码2.配置服务器环境3.部署到服务器(php5.5+mysql5.6/5.7+apache(nginx),windows,linu...
- 一周热门
- 最近发表
- 标签列表
-
- curseforge官网网址 (16)
- 外键约束 oracle (36)
- oracle的row number (32)
- 唯一索引 oracle (34)
- oracle in 表变量 (28)
- oracle导出dmp导出 (28)
- oracle 数据导出导入 (16)
- oracle两个表 (20)
- oracle 数据库 字符集 (20)
- oracle修改端口 (15)
- oracle安装补丁 (19)
- matlab归一化 (16)
- 共轭梯度法matlab (16)
- matlab化简多项式 (20)
- 多线程的创建方式 (29)
- 多线程 python (30)
- java多线程并发处理 (32)
- 宏程序代码一览表 (35)
- c++需要学多久 (25)
- c语言编程小知识大全 (17)
- css class选择器用法 (25)
- css样式引入 (30)
- html5和css3新特性 (19)
- css教程文字移动 (33)
- php简单源码 (36)