思考并回答以下问题:
函数式编程
函数式编程是一种声明式编程范式,它将代码抽象为纯的,不可变的,无副作用的函数,从而使程序员可以将这些函数组合在一起,从而使程序易于推理。
函数式编程的函数通常称为纯函数,具有几个重要特征,可以用PHP的语法模仿但不强制使用。纯函数具有以下特征:
- 引用透明
- 无副作用
- 没有外部依赖性
在接下来的几章中,我将详细讨论这些函数的含义,但它们归结为一个功能齐全的小型“黑盒子”,该函数接受定义明确的输入,产生定义明确的输出,以及给定相同的输入总是产生相同的输出。 特别是,该函数仅作用于给定的输入(它不考虑任何外部状态或数据,而仅依赖于被调用的参数),并且它唯一的作用就是返回一些输出( 每次您输入相同的输入时,输入的内容都会相同); 因此,它不会改变程序或系统自身之外的状态。
我将讨论不变性,即本质上是无法更改值的。起初这似乎是一个弊端,而不是收益,但是当在接下来的两章中将所有这些概念综合在一起时,您会发现不变性在函数式编程的灵活配方性质中起着关键作用,并且是其中之一。 这些因素使您可以轻松地推断出函数式代码。
Listing 2-3 backtrace.php
1 |
|
Listing 2-4 backtrace-output.txt
1 | #0 add_h_tags(TESTING) called at [backtrace.php:12] |
可变性和不变性
如果某些东西是可变的,则意味着您可以更改它。 变量是可变的。
在函数式编程中,您希望值(由函数表示)是不可变的。
PHP对不变性的支持有限,主要表现为使用define()函数或const关键字定义的“常量”形式。 在使用define()和const时,如何以及如何声明常量的声明之间有一些区别,但是一旦声明,由这两种方法创建的常量都是相同的。 两者的共同点是只有标量或数组才能为常数。 清单2-7尝试从包含匿名函数的变量中创建一个常量。 清单2-8显示了输出。
Listing 2-7. constant-func.php
1 |
|
Listing 2-8. constant-func-output.txt
1 | PHP Warning: Constants may only evaluate to scalar values or arrays in constant-func.php on |
在这里,您可以看到在尝试使用在define()中包含函数的变量时收到警告,并且当您尝试使用DOUBLE常量时,您将得到确认(通过致命错误),该确确实定义失败。
因此,在没有PHP太多帮助的情况下,您将需要在编码时通过纪律确保自己不变。帮助实现这一目标的关键方法之一是避免使用分配,而在阅读本书时,您将研究实现这一目标的方法。当您告诉他们您正在使用PHP进行函数式编程时,人们会指出PHP中缺乏对不变性的支持(与其他语言相比)。但是,它丝毫不会阻止您使用PHP编写功能程序。您只需要在编写代码时牢记它。
在观看自己的工作的同时,还需要留意PHP的工作。需要考虑的关键是PHP自身的函数如何对变量进行操作。例如,函数sort()对传递的数组进行突变(即排序),而不是返回作为旧数组排序版本的新数组(并使旧数组保持不变)。但是,您可以很容易地使自己的sort()的不可变版本(请参见清单2-9和清单2-10)。
Listing 2-9. sort.php
1 |
|
Listing 2-10. sort-output.txt
1 | Array |
之所以可行,是因为默认情况下,PHP函数参数是通过值而不是通过引用传递的。 这意味着在调用函数时,它会获取您作为参数提供的任何变量的副本,而不是对变量本身的引用。 该函数对该副本所做的任何操作均不会影响原始变量。
PHP确实允许您通过引用传递参数(sort()用来改变原始数组的引用),但这不是默认值。 传入对象或资源时,就是传入对象或资源变量,它是指向该对象或资源的指针。 该变量仍按值传递; 但是,变量的新副本仍指向原始对象或资源,因此它的行为与按值传递相似。 您将在第7章中深入探讨该问题。
在大多数情况下,显而易见的是,哪些函数会改变其参数。 他们通常不提供其输出作为返回值,但有些人则将按值和按引用参数混合使用,因此如果不确定,请务必查阅PHP手册。
什么是函数?
我将从头开始介绍函数,因为必须了解PHP如何实现函数的基础知识以及处理函数的不同方法,才能理解如何在PHP中实现函数编程。
在本章的过程中,您将对函数的确切功能有了更好的了解。 但这是一个很好的开始定义:
函数是一组指令,封装在一个独立的,可重用的代码块中。
PHP使您可以使用几种不同的函数调用,接下来将依次介绍。
命名函数
以下是命名函数的主要限制:
- 它们不能被销毁。
- 一旦定义,其功能(功能中的代码)就无法更改。
- 它们很难“传递”,因为它们无法分配给变量。
- 只能将函数名称分配给变量,而不是函数本身。
尽管可以通过动态方式处理已命名函数,但是call_user_func()函数确实提供了一种以这种方式工作的方法,如清单2-15和清单2-16所示,该方法的功能有限。
Listing 2-15. userfunc.php
1 |
|
Listing 2-16. userfunc-output.txt
1 | string(5) "mango" |
如您所见,您可以将函数的名称(作为字符串)传递给call_user_func()(加上要提供该函数的任何参数),并且call_user_func()将从您自己调用的函数中返回返回值返回值。如您所见,您可以在$ the_list中更改该函数的名称(因为它是一个字符串变量),然后再次运行call_user_func(),这一次运行另一个函数。
这可以使您具有一点活力,但功能有限。一种类似的方法称为变量函数,您将在下一节中对其进行介绍。从PHP 7.0开始,您还可以使用PHP闭包对象的fromCallable静态方法将命名函数包装到称为闭包的对象中,稍后将进行介绍。
命名函数的范围也不直观。正如您将在本章后面的“范围”部分中看到的那样,当您在函数中创建变量时,默认情况下,该函数之外的代码将无法使用该变量。但是,在另一个函数中实例化一个命名函数时,会在全局范围内创建该函数,以便可以从任何地方调用它,因此还需要具有全局唯一名称。考虑清单2-17中嵌套函数的演示,该函数返回一个字符串来说明其嵌套(清单2-18显示了输出)。
1 |
|
Listing 2-18. name-scope-output.txt
1 | string(1) "a" |
Listing 2-19. name-scope2.php
1 |
|
Listing 2-20. name-scope2-output.txt
1 | string(1) "a" |
Listing 2-21. name-scope3.php
1 |
|
Listing 2-22. name-scope3-output.txt
1 | string(3) "f()" |
可变函数
Listing 2-23. variable.php
1 |
|
Listing 2-24. variable-output.txt
1 | string(7) "chicken" |
Listing 2-25. constructs.php
1 |
|
Listing 2-26. constructs-output.php
1 | PHP Fatal error: Uncaught Error: Call to undefined function echo() in constructs.php:5 |
Listing 2-27. constructs2.php
1 |
|
Listing 2-28. constructs2-output.php
1 | hello world! |
返回值
Listing 2-29. null-return.php
1 |
|
Listing 2-30. null-return-output.txt
1 | NULL |
Listing 2-31. null-return2.php
1 |
|
Listing 2-32. null-return2-output.txt
1 | NULL |
Listing 2-33. return.php
1 |
|
Listing 2-34. return-output.txt
1 | int(23) |