PHP 中的函数式编程:高阶函数

如果您检查多个框架和大型应用程序,您肯定会在某个时候看到高阶函数。 许多语言都支持高阶函数的概念,包括 JavaScript、Java、.NET、Python 甚至 PHP 等等。

但是什么是高阶函数,我们为什么要使用它呢? 它给我们带来了什么好处,我们可以用它来简化我们的代码吗? 在本文中,我们将专门讨论 PHP 中的高阶函数,但将展示它们如何在其他语言中使用以进行比较。

一流的功能

在我们了解什么是高阶函数之前,我们必须首先了解我们必须有一种语言可以支持称为一等函数(也称为一阶函数)的特性。 这意味着该语言将函数视为一等公民。 换句话说,该语言对待函数就像对待变量一样。 您可以将函数存储在变量中,将其作为变量传递给其他函数,从函数返回它们,例如变量,甚至将它们存储在数据结构中,例如数组或对象属性,就像您可以使用变量一样。 现在大多数现代语言都默认具有此功能。 您真正需要知道的是函数可以像变量一样传递和使用。

出于我们的目的,我们将主要关注将函数作为参数传递和将函数作为结果返回,并简要介绍非局部变量和闭包的概念。 (您可以在上一段中链接到的维基百科文章的第 1.1、1.4 和 1.3 节中阅读有关这些概念的更多信息。)

什么是高阶函数?

识别高阶函数有两个主要特征。 高阶函数可以实现以下思想中的一个或两个:将一个或多个函数作为输入或返回一个函数作为输出的函数。 在 PHP 中,有一个关键字明确表明函数是高阶的:关键字 callable. 虽然不一定要出现此关键字,但关键字可以很容易地识别它们。 如果您看到具有可调用参数的函数或方法,则表示它采用函数作为输入。 另一个简单的迹象是,如果你看到一个函数返回一个使用它的函数 return 陈述。 return 语句可能只是函数的名称,甚至可以是匿名/内联函数。 以下是每种类型的一些示例。

// Simple user-defined function we'll pass to our higher-order function
function echoHelloWorld() {
  echo "Hello World!";
}

// Higher-order function that takes a function as an input and calls it
function higherOrderFunction(callable $func) {
  $func();
}

// Call our higher-order function and give it our echoHelloWorld() function. 
// Notice we pass just the name as a string and no parenthesis.
// This echos "Hello World!"
higherOrderFunction('echoHelloWorld'); 

以下是返回函数的高阶函数的一些简单示例:

// Returns an existing function in PHP called trim(). Notice it's a simple string with no parentheses.
function trimMessage1() {
  return 'trim';
}

// Here's an example using an anonymous inline function
function trimMessage2() {
    return function($text) {
        return trim($text);
    };
}

// Returns the 'trim' function
$trim1 = trimMessage1(); 

// Call it with our string to trim
echo $trim1('  hello world  ');

// Returns the anonymous function that internally calls 'trim'
$trim2 = trimMessage1(); 

// Call it again with our string to trim
echo $trim2('  hello world  ');

正如您想象的那样,您可以接收一个函数并使用它生成另一个返回的函数。 非常巧妙的把戏,对吧? 但是你为什么要这样做呢? 这听起来像是只会让事情变得更复杂的事情。

为什么要使用或创建高阶函数?

您可能出于多种原因希望在代码中创建高阶函数。 从代码灵活性、代码重用、代码扩展或模仿您在另一个程序中看到的代码解决方案的一切。 虽然原因很多,但我们将在此处介绍其中的一些。

增加代码灵活性

高阶函数增加了大量的灵活性。 根据前面的例子,您可能会看到类似这样的东西的一些用途。 您可以编写一个函数,它接受一整套不同的函数并使用它们,而无需编写代码来单独执行它们。

也许高阶函数本身并不知道它会接收到什么类型的函数。 谁说函数必须知道它正在接受的函数的任何信息? 一分钟输入函数可能是一个 add() 函数,接下来它可能是一个 divide() 功能。 无论哪种情况,它都可以正常工作。

轻松扩展您的代码

此功能还使以后添加其他输入功能变得更加容易。 假设你有 add()divide()但在路上你需要添加一个 sum() 功能。 你可以写 sum() 函数并将其通过高阶函数进行管道传输,而无需更改它。 在后面的示例中,我们将演示如何使用此功能来创建我们自己的 calc() 功能。

模仿另一种语言的特征

您可能希望在 PHP 中使用高阶函数的另一个原因是模拟 Python 中类似装饰器的行为。 您将一个函数“包装”在另一个函数中以修改该函数的行为方式。 例如,您可以编写一个对其他函数计时的函数。 您编写一个高阶函数,它接受一个函数,启动一个计时器,调用该函数,然后结束计时器以查看经过了多少时间。

PHP 中现有的高阶函数示例

在 PHP 中查找高阶函数的示例非常简单。 查看 PHP 文档并找到一个函数/方法 callable 输入参数,你已经找到了一个。 下面是一些常用的高阶函数的例子。 您甚至可能在不知道它们是高阶函数的情况下使用过它们。

高阶动态二重奏: array_maparray_filter

$arrayOfNums = [1,2,3,4,5];

// array_map: example of taking an inline anonymous function
$doubledNums = array_map(function($num) {
  return $num * 2;
}, $arrayOfNums);

var_dump($doubledNums); // Prints out array containing [2, 4, 6, 8, 10];

让我们看看如何使用另一个,这次使用我们单独定义的过滤器函数:

$arrayOfNums = [1,2,3,4,5];

// Example of creating a function and giving it to a higher-order function array_filter()
function isEven($num) {
  return ($num % 2) === 0;
}

// array_filter is a higher-order function
$evenNums = array_filter($arrayOfNums, 'isEven');

var_dump($evenNums); // Prints out array containing [2, 4]

array_filterarray_map 是两个非常流行的高阶函数,您会在大量代码项目中找到它们。 您可能会使用的另一个是 call_user_func,它接受一个函数和一个参数列表,并调用该函数。 这本质上是一个定制的高阶函数。 它的全部目的是调用用户定义的其他函数:

function printCustomMessage($message) {
  echo "$message";
}

call_user_func('printCustomMessage', 'Called custom message through the use of call_user_func!');

如何创建自己的高阶函数

我们已经展示了许多关于高阶函数如何在 PHP 中工作的示例,并且我们已经创建了一些自定义函数来演示它们的各种用途。 但是,让我们通过一个我们将调用的新函数来更多地展示灵活性 calc(). 这个函数将接受两个参数和一个函数,该函数将对这两个参数进行操作以给我们一个结果:

function add($a, $b) {
  return $a + $b;
}

function subtract($a, $b) {
  return $a - $b;
}

function multiply($a, $b) {
  return $a * $b;
}

// Higher-order function that passes along $n1 and $n2
function calc($n1, $n2, $math_func) {
  return $math_func($n1, $n2);
}

$addedNums = calc(1, 5, 'add');
$subtractedNums = calc(1, 5, 'subtract');
$multipliedNums = calc(1, 5, 'multiply');

// Prints 6
echo "Added numbers: $addedNumsn";

// Prints -4
echo "Subtracted numbers: $subtractedNumsn";

// Prints 5
echo "Multiplied numbers: $multipliedNumsn";

请注意我们的 calc() 函数的编写非常通用,可以接收一组其他函数并使用参数调用它们 $n1$n2. 在这种情况下,我们的计算函数不关心它接收的函数是做什么的。 它只是将参数传递给函数并返回结果。

但是等一下:我们的老板刚刚要求我们在现有系统中添加一个将两个字符串相加的功能。 但是连接字符串并不是典型的数学运算。 然而,通过我们这里的设置,我们只需要添加字符串连接并将其通过管道 calc():

function addTwoStrings($a, $b) {
  return $a . " " . $b;
}

// Prints "Hello World!"
$concatenatedStrings = calc('Hello', 'World!', 'addTwoStrings');

虽然此示例非常人为,但它演示了如何在更大的项目中使用该概念。 也许高阶函数决定了事件的处理方式。 也许,根据触发的事件,您可以通过传入的处理函数来路由它。可能性是无限的。

这与其他具有高阶函数的语言相比如何? 让我们以 Python 中的一个简单示例装饰器为例,将其与 JavaScript 进行比较,然后将其与 PHP 进行比较。 这样,如果你懂 Python 或 JS,就可以看出它们是如何等效的。

与 Python 和 JavaScript 的并排比较

def say_message(func):
    def wrapper():
        print('Print before function is called')
        func()
        print('Print after function is called')
    return wrapper

def say_hello_world():
    print("Hello World!")

# Pass our hello world function to the say_message function. 
# It returns a function that we can then call
# Note: This line could have been replaced with the @say_message (pie syntax) on the say_hello_world method
say_hello_world = say_message(say_hello_world)

# Call the created function
say_hello_world()

# Output...
# Print before function is called
# Hello World!
# Print after function is called

现在让我们看看如何在 JavaScript 高阶函数中实现这一点:

// Takes a function, returns a function.
function say_message(func) {
  return function() {
      console.log("Print before function is called");
      func();
      console.log("Print after function is called");
  }
}

function say_hello_world() {
  console.log("Hello World!");
}

// Again pass our function to say_message as an input parameter
// say_message returns a function that is wrapped around the say_hello_world function
say_hello = say_message(say_hello_world);

// Call the function
say_hello();

// Output...
// Print before function is called
// Hello World!
// Print after function is called

最后,让我们将其与 PHP 进行比较:

// Takes a function, returns a function.
function say_message($func) {
  // We use 'use' so that our anonymous function can see $func coming in
  return function() use ($func) {
      echo "Print before function is called";
      $func();
      echo "Print after function is called";
  };
}

function say_hello_world() {
  echo "Hello World!";
}

// Again pass our function to say_message as an input parameter
// say_message returns a function that is wrapped around the say_hello_world function
$say_hello = say_message('say_hello_world');

// Call the function
$say_hello();

// Output...
// Print before function is called
// Hello World!
// Print after function is called

从并排比较可以看出,PHP 版本与 JavaScript 语法非常相似。 但是,如果您对 Python 和装饰器有所了解,您就会明白装饰器是如何被翻译成其他两种编程语言之一的。

快速了解 Lambdas/匿名函数

很多时候,当您使用高阶函数时,您可能会看到内联匿名函数(也称为 lambda)的使用。 在我们上面看到的一些示例中,我使用了这种格式。 除此之外,我还使用了更明确的版本,即首先定义函数,然后将其传递给我们的高阶函数。 这是为了让您习惯看到这两个版本。 通常情况下,您会在大量使用高阶函数的框架中看到内联 lambda 版本。 不要让这种格式让你失望。 他们只是创建一个简单的函数,没有名称,并使用它来代替您给出独立函数名称的地方。

结论

在本文中,我们介绍了使函数成为高阶函数的基本定义。 任何接受一个函数或返回一个函数(或两者)的函数都可以被认为是高阶函数。 我们还谈到了为什么你…

阅读更多

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注