0x00 何为函数式编程

网上已经有好多详细的接受了,我认为比较重要的有:

  1. 函数是“第一等公民”,即函数和其它数据类型一样处于平等地位
  2. 使用“表达式”(指一个单纯的运算过程,总是有返回值),而不是“语句”(执行操作,没有返回值)
  3. 没有”副作用“,即不修改外部值

0x01 开始函数式编程

在此之前,请先了解PHP中的匿名函数和闭包,可以参考我写得博客

函数式编程有两个最基本的运算:合成和柯里化。

函数合成

函数合成,即把多个函数的运算合成一个函数,如

A=f(x)

B=g(x)

C=f(g(x))

那么C即是A和B的合成。

用代码表示为:

$compose = function ($f,$g){
return function ($x) use($f,$g){ //这里返回一个函数的函数,即高阶函数
return $f($g($x));
};
}; function addTen($a){
return $a+10;
} function subOne($a){
return $a-1;
} $z = $compose('addTen','subOne');//如果使用 $addOne = function(){}的形式,可以直接传变量
echo $z(5);// 14

要求合成的函数也是个纯函数,如果不是纯函数,那么结果不一致,怎么合成呢?

compose返回一个高阶函数,当给合成的这个函数传值时,变回在高阶函数内部调用之前保存的函数。

柯里化

可以看到如果这里传入的函数参数有多个,那么上面的合成函数就失效了。

这里就要请出另外一个函数式编程使用到的另外一个大神了,柯里化。“所谓"柯里化",就是把一个多参数的函数,转化为单参数函数"。

//柯里化之前
function add($a,$b){
return $a+$b;
}
add(1, 2); // 3 // 柯里化之后
function addX($b) {
return function ($a) use($b) {
return $a + $b;
};
} $addTwo = addX(2);
$addTwo(1);//3

PHP7以下直接调用addX(2)(1),会报错,所以上面使用了中间变量$addTwo。

Parse error: syntax error, unexpected '('

在PHP7以上完善了一致变量语法,而且PHP7速度更快,强烈建议使用PHP7。

通用柯里化,柯里化很美好,然而我们不可能为每一个函数写一遍,那么有没有包装函数,可以把普通的函数改些为柯里化后的函数呢?

代码如下:(摘自:pramda)

function curry2($callable)
{
return function () use ($callable) {
$args = func_get_args();
switch (func_num_args()) {
case 0:
throw new \Exception("Invalid number of arguments");
break;
case 1:
return function ($b) use ($args, $callable) {
return call_user_func_array($callable, [$args[0], $b]);
};
break;
case 2:
return call_user_func_array($callable, $args);
break;
default:
// Why? To support passing curried functions as parameters to functions that pass more that 2 parameters, like reduce
return call_user_func_array($callable, [$args[0], $args[1]]);
break;
}
};
}
function add($a,$b){
return $a+$b;
} $addCurry = curry2('add');
$addTwo = $addCurry(2);
$addTwo(1);//3

说明,curry2返回一个闭包(如上面的$addCurry),当这个闭包被调用时会通过func_get_args动态获取参数,

以及func_num_args动态获取参数个数。curry2函如其名,可以给把参数个数为两个函数柯里化。于是在闭包里,我们看到,

在对参数个数进行判断,当参数个数为1时,则生成新的闭包(如上面的$addTwo),新的闭包里保存原函数以及整个参数,

当新闭包被调用时,则调用call_user_func_array传入原函数、保存的参数、新参数,获取了想要的结果。

扩展,函数式编程还有另外一个重要的概念,函子(即带有map方法的类),更多内容可以看阮老师的这两篇文章,我就不详叙了。

平常我们自己使用的函数,如果符合函数式编程的思想,也可以柯里化。当然对于更多参数的函数得运用更高阶的curryN来柯里化。

这些已经有人造好轮子了,下面开始进入正题了。

0x02 Ramda

这个Ramda实际上是函数式编程中的Pointfree风格。

在Ramda里,数据一律放在最后一个参数,理念是"function first,data last"。

比如

//例1
function map(){
$args = func_get_args();
$n = func_num_args();
$callable = $args[$n-1];
unset($args[$n-1]);
$res = [];
foreach ($args as $v){
if(is_array($v)){
foreach ($v as $i){
$res[] = call_user_func($callable,$i);
}
}else{
$res[] = call_user_func($callable,$v);
}
}
return $res;
}
map(1,2,'square');//1,4
map([1,2],'square');// 1,4 //例2
function square($v)
{
return($v*$v);
}
array_map("square",[1,2]); //1 ,4

上面的代码,例1就不是Ramda风格,而例2则是Ramda风格。

既然有人造好轮子了,那么我们直接用就好啦,下面请出主角,pramda,Ramda风格的PHP函数式编程库。

安装

composer require kapolos/pramda

如果出现

[InvalidArgumentException]

Could not find package kapolos/pramda.

可以在composer.json里加入 "kapolos/pramda":"dev-master"

示例:

$before = [1,2,3,4,5];
$after = P::map(function($num) {
return $num * 2;
}, $before);
P::toArray($after); //=> [2,4,6,8,10] $addOne = P::add(1);
$divTen = P::divide(10); //10是被除数
$fn1 = P::compose($addOne,$divTen); //compose从右往左
$fn2 = P::pipe($addOne,$divTen);//pipe从左往右 echo $fn1(1); //11
echo "\n";
echo $fn2(1); //5

不足之处,pramda不支持占位符,另外curry函数最多只支持3个参数。

另外有也有两个函数式编程库,functional-phpdash可惜不是Ramda风格的。

正如阮老师所提到的

学习函数式编程,实际上就是学习函子的各种运算。

如果想了解更多,可以继续阅读阮老师的这两篇文章。

Ramda函数式编程之PHP的更多相关文章

  1. Python函数式编程之map()

    Python函数式编程之map() Python中map().filter().reduce()这三个都是应用于序列的内置函数. 格式: map(func, seq1[, seq2,…]) 第一个参数 ...

  2. python3 第二十一章 - 函数式编程之return函数和闭包

    我们来实现一个可变参数的求和.通常情况下,求和的函数是这样定义的: def calc_sum(*args): ax = 0 for n in args: ax = ax + n return ax 但 ...

  3. python3 第二十章 - 函数式编程之Higher-order function(高阶函数)

    什么是高阶函数?把函数作为参数传入或把函数做为结果值返回,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式.函数式编程的特点: 函数本身可以赋值给变量,赋值后变量为函数: 允许将函数本身 ...

  4. 函数式编程之foldLeftViaFoldRight

    问题来自 Scala 函数式编程 一书的习题, 让我很困扰, 感觉函数式编程有点神学的感觉.后面看懂之后, 又觉得函数式编程所提供的高阶抽象是多么的强大. 这个问题让我发呆了好久, 现在把自己形成的想 ...

  5. Python函数式编程之lambda表达式

    一:匿名函数的定义 lambda parameter_list: expression 二:三元表达式 条件为真时返回的结果 if 条件判断 else 条件为假的时候返回的结果 三:map map(f ...

  6. 函数式编程之-bind函数

    Bind函数 Bind函数在函数式编程中是如此重要,以至于函数式编程语言会为bind函数设计语法糖.另一个角度Bind函数非常难以理解,几乎很少有人能通过简单的描述说明白bind函数的由来及原理. 这 ...

  7. 函数式编程之-F#类型系统

    在深入到函数式编程思想之前,了解函数式独有的类型是非常有必要的.函数式类型跟OO语言中的数据结构截然不同,这也导致使用函数式编程语言来解决问题的思路跟OO的思路有明显的区别. 什么是类型?类型在编程语 ...

  8. 函数式编程之-Partial application

    上一篇关于Currying的介绍,我们提到F#是如何做Currying变换的: let addWithThreeParameters x y z = x + y + z let intermediat ...

  9. 函数式编程之-Currying

    这个系列涉及到了F#这门语言,也许有的人觉得这样的语言遥不可及,的确我几乎花了2-3年的时间去了解他:也许有人觉得学习这样的冷门语言没有必要,我也赞同,那么我为什么要花时间去学习呢?作为一门在Tiob ...

随机推荐

  1. poj2230 Watchcow【欧拉回路】【输出路径】(遍历所有边的两个方向)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4392 题目大意: 一个图,要将每条边恰好遍历两遍,而且要以不同的方向,还要回到原点. dfs解法    ...

  2. P1799 数列_NOI导刊2010提高(06)

    P1799 数列_NOI导刊2010提高(06)f[i][j]表示前i个数删去j个数得到的最大价值.if(i-j==x) f[i][j]=max(f[i][j],f[i-1][j]+1); else ...

  3. python有序字典OrderedDict()

    转python创建有序字典OrderedDict # -*- coding:utf-8 -*- """ python有序字典 需导入模块collections " ...

  4. python魔法方法-属性访问控制

    属性访问控制 所谓的属性访问控制就是控制点号访问属性的行为,而且不仅是类的外部,连类的内部也受控制,代码见真章,边看代码边解释: __getattr__(self, item) 定义当访问不存在的属性 ...

  5. 项目冲刺 Seventh

    Seventh Sprint 1.各个成员今日完成的任务 蔡振翼:编写博客 谢孟轩:消息功能的实现,各页面与功能的调试优化 林凯:优化注册判断逻辑,整合相关代码 肖志豪:帮助组员 吴文清:完成管理员信 ...

  6. 潭州课堂25班:Ph201805201 第十六课 正则 (课堂笔记)

    import re 元字符: . ^ $ * + ? {} \ () # s = 'abcdefg' # s.find('c') # print(s.find('cd')) ## 查找 # b = s ...

  7. ansible命令及模块

    ping 命令 #测试单个主机 [root@node1 opt]# ansible -m ping 10.0.0.22 #获取多个主机 [root@node1 opt]# ansible 10.0.0 ...

  8. Oracle取月份-不带前面的0

    出处:http://www.2cto.com/database/201208/145611.html   今天碰到只要取月份和天数,如果月份前面有0要去掉0.比如说2010-01-08 ,需要的结果是 ...

  9. 写自己的ASP.NET MVC框架(下)

    上篇博客[写自己的ASP.NET MVC框架(上)] 我给大家介绍我的MVC框架对于Ajax的支持与实现原理.今天的博客将介绍我的MVC框架对UI部分的支持. 注意:由于这篇博客是基于前篇博客的,因此 ...

  10. AngularJS转换请求内容

    在"AngularJS中转换响应内容"中,体验了如何转换响应内容.本篇来体验如何转换请求内容. 主页面通过onSend方法把request对象转递出去. <form name ...