Статьи

Повторное использование ваших замыканий с функторами

Мне нравятся замыкания PHP и их надмножества, аномичные функции, так как они очень хорошо реализуют шаблон Command при правильном использовании. Однако я чувствую, что иногда они:

  • трудно использовать повторно : если вы хотите повторно использовать замыкание или анонимную функцию в двух разных местах, вам придется делиться кодом создания, помещая его в общий класс или метод. У нас нет разделения между кодом создания (который для объекта будет новым оператором и кодом самого замыкания. Это немного похоже на eval ().
  • Трудно заставить контракты на . Вы не можете ввести подсказку о закрытии: даже если замыкание принимает пять аргументов, а другое вообще не имеет аргументов, вы не можете различить их как параметры в сигнатуре метода.

В последнем случае вы можете просто использовать недокументированную подсказку типа Closure, но она будет просто отлавливать, если объект передается рассматриваемому методу, а не неправильному замыканию.

function takesAsInputAnotherFunction(Closure $closure)

Этот хак явно указан как деталь реализации, на которую не следует полагаться в руководстве по PHP

__invoke () на помощь

Что, если бы мы вместо этого хотели, замыкание, которое мы можем создать даже несколько раз (возможно, с разными переменными), и что мы могли бы напечатать подсказку?
В этом случае нам нужно вернуться к объектно-ориентированной парадигме, определив класс, а затем использовать __invoke (), чтобы сохранить синтаксический сахар возможности вызывать его очень просто:

$object($argument1, $argument2);$object($argument1, $argument2);

Этот вид объекта называется функтором .

The definition of a class is longer than the creation code of a closure, so there is a trade-off: we have better specified code but we lose conciseness. Type hints are not only a defensive programming construct: they serve also as documentation as the next programmer reading a method’s signature would learn what he can pass in just from the parameters hints instead of going reading the tests or grepping for the method in his working copy:

grep -r '->methodName(' .

becomes just a quick look at the definition

public function methodName(AdderCallback $callback) { // ...

However the expressing power of an object implementing __invoke() and of a closure are equivalent, and when you find yourself rewriting the same closure over and over, you may want to keep it in only one place. You’ll have to create a class to hold the creation code anyway, so why not making it a class of its own?

For the PHP interpreter, closure and functors are really the same thing, and are even more swappable than callbacks built with array($object, ‘methodName’). is_callable() returns true for both of them, and you can write $closure() as well as $functor().

Example

Here is some code that you can hack if you want to play with closures and functors.

<?php
$square = function($value) {
return $value * $value;
};

var_dump($square(3));

/**
* a little more noisy, but equivalent in usage and functionality-
*/
class SquareCallback
{
public function __invoke($value)
{
return $value * $value;
}
}

$squareObject = new SquareCallback;
var_dump($squareObject(3));

// using PHP utilities which works on callback: total equivalency
var_dump(is_callable($squareObject));

$array = array(0, 1, 2);
var_dump(array_map($square, $array));
var_dump(array_map($squareObject, $array));

// currying vs collaborators: total equivalency
$toAdd = 5;
$adder = function($value) use ($toAdd) {
return $value + $toAdd;
};
var_dump($adder(3));

/**
* A LOT more noisy
*/
class AdderCallback
{
private $numberToAdd;

public function __construct($numberToAdd)
{
$this->numberToAdd = $numberToAdd;
}

public function __invoke($value)
{
return $value + $this->numberToAdd;
}
};
$adder = new AdderCallback(5);
var_dump($adder(3));

// but only classes can do this
function someMethodOrFunction(AdderCallback $adder)
{
// ...
}