虽然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源代码里,我们可以找到对应的实当代码。
<?phpnamespace PhalApi;classDependenceInjectionimplementsArrayAccess{ // …… / ------------------ 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容器的先容,先到这里。后面讲魔术方法时,我们再来一起深入磋商。