PHP7的函数式编程

思考并回答以下问题:

函数式编程

函数式编程是一种声明式编程范式,它将代码抽象为纯的,不可变的,无副作用的函数,从而使程序员可以将这些函数组合在一起,从而使程序易于推理。

函数式编程的函数通常称为纯函数,具有几个重要特征,可以用PHP的语法模仿但不强制使用。纯函数具有以下特征:

  • 引用透明
  • 无副作用
  • 没有外部依赖性

在接下来的几章中,我将详细讨论这些函数的含义,但它们归结为一个功能齐全的小型“黑盒子”,该函数接受定义明确的输入,产生定义明确的输出,以及给定相同的输入总是产生相同的输出。 特别是,该函数仅作用于给定的输入(它不考虑任何外部状态或数据,而仅依赖于被调用的参数),并且它唯一的作用就是返回一些输出( 每次您输入相同的输入时,输入的内容都会相同); 因此,它不会改变程序或系统自身之外的状态。

我将讨论不变性,即本质上是无法更改值的。起初这似乎是一个弊端,而不是收益,但是当在接下来的两章中将所有这些概念综合在一起时,您会发现不变性在函数式编程的灵活配方性质中起着关键作用,并且是其中之一。 这些因素使您可以轻松地推断出函数式代码。

Listing 2-3 backtrace.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
function prepare_text($text)
{
return make_headline($text);
}

function make_headline($text)
{
return add_h_tags( upper_case($text) );
}

function upper_case($text)
{
return strtoupper($text);
}

function add_h_tags($text)
{
debug_print_backtrace();
return '<h1>'.$text.'</h1>';
}

$title = prepare_text('testing');
echo $title;

Listing 2-4 backtrace-output.txt

1
2
3
4
#0 add_h_tags(TESTING) called at [backtrace.php:12]
#1 make_headline(testing) called at [backtrace.php:6]
#2 prepare_text(testing) called at [backtrace.php:30]
<h1>TESTING</h1>

可变性和不变性

如果某些东西是可变的,则意味着您可以更改它。 变量是可变的。

在函数式编程中,您希望值(由函数表示)是不可变的。

PHP对不变性的支持有限,主要表现为使用define()函数或const关键字定义的“常量”形式。 在使用define()和const时,如何以及如何声明常量的声明之间有一些区别,但是一旦声明,由这两种方法创建的常量都是相同的。 两者的共同点是只有标量或数组才能为常数。 清单2-7尝试从包含匿名函数的变量中创建一个常量。 清单2-8显示了输出。

Listing 2-7. constant-func.php

1
2
3
4
5
6
7
8
9
10
<?php
$double = function ($input)
{
return $input * 2;
};

define('DOUBLE', $double);

echo "Double 2 is " . $double(2) . "\n";
echo "Double 2 is " . DOUBLE(2) . "\n";

Listing 2-8. constant-func-output.txt

1
2
3
4
5
6
7
8
PHP Warning: Constants may only evaluate to scalar values or arrays in constant-func.php on
line 8
Double 2 is 4
PHP Fatal error: Uncaught Error: Call to undefined function DOUBLE() in constant-func.
php:12
Stack trace:
#0 {main}
thrown in constant-func.php on line 12

在这里,您可以看到在尝试使用在define()中包含函数的变量时收到警告,并且当您尝试使用DOUBLE常量时,您将得到确认(通过致命错误),该确确实定义失败。

因此,在没有PHP太多帮助的情况下,您将需要在编码时通过纪律确保自己不变。帮助实现这一目标的关键方法之一是避免使用分配,而在阅读本书时,您将研究实现这一目标的方法。当您告诉他们您正在使用PHP进行函数式编程时,人们会指出PHP中缺乏对不变性的支持(与其他语言相比)。但是,它丝毫不会阻止您使用PHP编写功能程序。您只需要在编写代码时牢记它。

在观看自己的工作的同时,还需要留意PHP的工作。需要考虑的关键是PHP自身的函数如何对变量进行操作。例如,函数sort()对传递的数组进行突变(即排序),而不是返回作为旧数组排序版本的新数组(并使旧数组保持不变)。但是,您可以很容易地使自己的sort()的不可变版本(请参见清单2-9和清单2-10)。

Listing 2-9. sort.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
function immutable_sort($array)
{
sort($array);
return $array;
}

$vegetables = ['Carrot', 'Beetroot', 'Asparagus'];

# Sort using our immutable function
$ordered = immutable_sort( $vegetables );
print_r( $ordered );

# Check that $vegetables remains unmutated
print_r( $vegetables );

# Do it the mutable way
sort( $vegetables );

# And see that the original array is mutated
print_r( $vegetables );

Listing 2-10. sort-output.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Array
(
[0] => Asparagus
[1] => Beetroot
[2] => Carrot
)
Array
(
[0] => Carrot
[1] => Beetroot
[2] => Asparagus
)
Array
(
[0] => Asparagus
[1] => Beetroot
[2] => Carrot
)

之所以可行,是因为默认情况下,PHP函数参数是通过值而不是通过引用传递的。 这意味着在调用函数时,它会获取您作为参数提供的任何变量的副本,而不是对变量本身的引用。 该函数对该副本所做的任何操作均不会影响原始变量。
PHP确实允许您通过引用传递参数(sort()用来改变原始数组的引用),但这不是默认值。 传入对象或资源时,就是传入对象或资源变量,它是指向该对象或资源的指针。 该变量仍按值传递; 但是,变量的新副本仍指向原始对象或资源,因此它的行为与按值传递相似。 您将在第7章中深入探讨该问题。

在大多数情况下,显而易见的是,哪些函数会改变其参数。 他们通常不提供其输出作为返回值,但有些人则将按值和按引用参数混合使用,因此如果不确定,请务必查阅PHP手册。

什么是函数?

我将从头开始介绍函数,因为必须了解PHP如何实现函数的基础知识以及处理函数的不同方法,才能理解如何在PHP中实现函数编程。

在本章的过程中,您将对函数的确切功能有了更好的了解。 但这是一个很好的开始定义:

函数是一组指令,封装在一个独立的,可重用的代码块中。

PHP使您可以使用几种不同的函数调用,接下来将依次介绍。

命名函数

以下是命名函数的主要限制:

  • 它们不能被销毁。
  • 一旦定义,其功能(功能中的代码)就无法更改。
  • 它们很难“传递”,因为它们无法分配给变量。
  • 只能将函数名称分配给变量,而不是函数本身。

尽管可以通过动态方式处理已命名函数,但是call_user_func()函数确实提供了一种以这种方式工作的方法,如清单2-15和清单2-16所示,该方法的功能有限。

Listing 2-15. userfunc.php

1
2
3
4
5
6
7
8
9
10
11
<?php
function list_fruit($item) {
return ['apple','orange','mango'][$item];
}
function list_meat($item) {
return ['pork','beef','human'][$item];
}
$the_list = 'list_fruit';
var_dump( call_user_func($the_list, 2) );
$the_list = 'list_meat';
var_dump( call_user_func($the_list, 1) );

Listing 2-16. userfunc-output.txt

1
2
string(5) "mango"
string(4) "beef"

如您所见,您可以将函数的名称(作为字符串)传递给call_user_func()(加上要提供该函数的任何参数),并且call_user_func()将从您自己调用的函数中返回返回值返回值。如您所见,您可以在$ the_list中更改该函数的名称(因为它是一个字符串变量),然后再次运行call_user_func(),这一次运行另一个函数。

这可以使您具有一点活力,但功能有限。一种类似的方法称为变量函数,您将在下一节中对其进行介绍。从PHP 7.0开始,您还可以使用PHP闭包对象的fromCallable静态方法将命名函数包装到称为闭包的对象中,稍后将进行介绍。

命名函数的范围也不直观。正如您将在本章后面的“范围”部分中看到的那样,当您在函数中创建变量时,默认情况下,该函数之外的代码将无法使用该变量。但是,在另一个函数中实例化一个命名函数时,会在全局范围内创建该函数,以便可以从任何地方调用它,因此还需要具有全局唯一名称。考虑清单2-17中嵌套函数的演示,该函数返回一个字符串来说明其嵌套(清单2-18显示了输出)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

function a()
{
function b()
{
return "a -> b";
}

return "a";
}

function c()
{
function d()
{
function e()
{
return "c -> d -> e";
}

return "c -> d";
}

return "c";
}

var_dump( a() );

var_dump( b() );

var_dump( c() );

var_dump( d() );

var_dump( e() );

Listing 2-18. name-scope-output.txt

1
2
3
4
5
string(1) "a"
string(6) "a -> b"
string(1) "c"
string(6) "c -> d"
string(11) "c -> d -> e"

Listing 2-19. name-scope2.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
function a()
{
function b() {
return "a -> b";
}
return "a";
}

function c()
{
function d()
{
function e()
{
return "c -> d -> e";
}
return "c -> d";
}

return "c";
}

var_dump( a() );
var_dump( b() );
var_dump( d() );
var_dump( c() );
var_dump( e() );

Listing 2-20. name-scope2-output.txt

1
2
3
4
5
6
string(1) "a"
string(6) "a -> b"
PHP Fatal error: Uncaught Error: Call to undefined function d() in name-scope2.php:38
Stack trace:
#0 {main}
thrown in name-scope2.php on line 38

Listing 2-21. name-scope3.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
function f()
{
function g()
{
return "1st g()";
};
return "f()";
}

function h()
{
function g()
{
return "2nd g()";
};
return "h()";
}

var_dump( f() );
var_dump( g() );
var_dump( h() );

Listing 2-22. name-scope3-output.txt

1
2
3
4
string(3) "f()"
string(7) "1st g()"
PHP Fatal error: Cannot redeclare g() (previously declared in name-scope3.php:7) in name-
scope3.php on line 17

可变函数

Listing 2-23. variable.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
function vehicles( $index )
{
$types = ["car", "motorbike", "tractor"];
return $types[$index];
}

function animals( $index )
{
$types = ["cow", "pig", "chicken", "horse"];
return $types[$index];
}

$get_thing = 'animals'; # string with the name of a function
var_dump( $get_thing(2) ); # add ($index) to call it

$get_thing = 'vehicles'; # change the function
var_dump( $get_thing(2) ); #same "code", different function

# Just to show that $get_thing is just a
# standard string, and nothing special...
$get_thing = strrev('selcihev'); # do string things
var_dump( $get_thing ); # it's a string
var_dump( $get_thing(2) ); # call it
var_dump( $get_thing ); # afterwards, still just a string
unset( $get_thing ); # we can destroy it, because it's a string
var_dump( $get_thing );
var_dump( vehicles(2) ); # But the function still exists

# However, it needs to be set to a function that exists
$get_thing = 'people';
var_dump( $get_thing(2) );

Listing 2-24. variable-output.txt

1
2
3
4
5
6
7
8
9
10
11
12
string(7) "chicken"
string(7) "tractor"
string(8) "vehicles"
string(7) "tractor"
string(8) "vehicles"
PHP Notice: Undefined variable: get_thing in variable.php on line 41
NULL
string(7) "tractor"
PHP Fatal error: Uncaught Error: Call to undefined function people() in variable.php:49
Stack trace:
#0 {main}
thrown in variable.php on line 49

Listing 2-25. constructs.php

1
2
3
<?php
$var_func = 'echo';
$var_func('hello world!');

Listing 2-26. constructs-output.php

1
2
3
4
PHP Fatal error: Uncaught Error: Call to undefined function echo() in constructs.php:5
Stack trace:
#0 {main}
thrown in constructs.php on line 5

Listing 2-27. constructs2.php

1
2
3
4
5
6
7
<?php
function my_echo($string) {
echo $string;
}

$var_func = 'my_echo';
$var_func('hello world!');

Listing 2-28. constructs2-output.php

1
hello world!

返回值

Listing 2-29. null-return.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
function reverse($string)
{
$string = strrev($string);
}

function capitals($string)
{
if ($string != 'banana')
{
$string = strtoupper($string);
return $string;
}
}
# no return statement
var_dump( reverse('hello') );

# returns a value
var_dump( capitals('peaches') );

# execution flow misses return statement
var_dump( capitals('banana') );

Listing 2-30. null-return-output.txt

1
2
3
NULL
string(7) "PEACHES"
NULL

Listing 2-31. null-return2.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
function fruits($type)
{
if ($type == 'mango')
{
return 'Yummy!';
}
else
{
return;
}
}

var_dump( fruits('kiwi') );
var_dump( fruits('pomegranate') );
var_dump( fruits('mango') );

Listing 2-32. null-return2-output.txt

1
2
3
NULL
NULL
string(6) "Yummy!"

Listing 2-33. return.php

1
2
3
4
5
6
7
8
9
10
11
<?php
function my_funct()
{
$a = 23;
return $a;

$a = 45;
return $a;
}

var_dump( my_funct() );

Listing 2-34. return-output.txt

1
int(23)
0%