Генераторы в PHP 5.5
Самым большим нововведением (и очень важным для языка) в PHP 5.5.0 будут генераторы. Я установил себе альфа-версию 5.5.0, хотел посмотреть поближе что да как, генераторы мне нравятся в Пайтоне и я рад, что PHP ими обзаведётся.
Посмотрите на код, который я написал, пока мы ждали всех участников встречи, на которую я был приглашён:
<?
function enumerate($arr)
{
$i = 0;
foreach ($arr as $value) {
yield [$i++, $value];
}
}
function ifilter(callable $predicate = null, $arr)
{
if ($predicate === null) {
$predicate = 'boolval';
}
foreach ($arr as $value) {
if ($predicate($value)) {
yield $value;
}
}
}
function islice($arr, $start, $stop)
{
if (is_array($arr)) {
reset($arr);
for ($i = $start; $i > 0 && each($arr); $i--);
for ($i = $stop - $start + 1; $i > 0 && list(,$value) = each($arr); $i--) {
yield $value;
}
} else {
for ($i = $start; $i > 0 && $arr->valid(); $i--) {
$arr->next();
}
for ($i = $stop - $start; $i > 0 && $arr->valid(); $i--) {
yield $arr->current();
$arr->next();
}
}
}
$array = [10, 20, 30, 33, 40, 50, 60];
$gen = islice(
enumerate(
ifilter(
function($x) { return ($x % 10) === 0; },
$array
)),
1, 4
);
foreach ($gen as $value) {
print_r($value);
}
Три генератора, пропускающие через себя данные, обрабатывают массив, всё работает. Названия взяты из Пайтона. Первый нумерует входящий поток данных (возвращает пары номер и значение), второй фильтрует значения через функцию, третий отрезает заданную часть от входных данных.
Концепция генераторов проста, как герань. PHP поддерживает два способа их создания (как и Пайтон), но мне милее оператор yield. Работает он примерно как return, но при повторном входе в функцию её выполнение происходит не сначала, а с того места, откуда управление было возвращено при помощи yield. Можно ещё передавать значения внутрь генератора, но я про это сейчас не буду. Вцелом, получаются настоящие ленивые вычисления — всё это вызывается ровно тогда, когда это реально нужно.
Генераторы, обычно, работают с итерируемыми объектами — возращают их и/или принимают. Итерируемый объект, проще говоря, это то, по чему можно пробежаться циклом. Генераторы, кстати, тоже итерируемые.
И вот тут опять проявляются недостатоки PHP: отсутствие общей логики, непродуманность.
Массив тут до сих пор примитивный тип и он требует иной способ обработки. Посмотрите на генератор islice, тут проблема видна во всей красе. Начинается она уже с хинтинга, я не могу потребовать от $arr быть типа array, потому что на вход может прийти другой генератор, и указать класс генератора тоже не могу — массив ему не принадлежит. Думаю, следует ожидать появления нового типа «iterable», как это уже случилось с callable.
Вторая проблема ещё более серьёзная. У массива нет методов (он же примитивный тип), а генераторы, почему-то, не работают в процедурном стиле. Поэтому мне приходится делить логику на две части: отдельно писать её для массива и для генераторов. Если я задумаю поддержать ещё и строки (в Пайтоне они отлично итерируются), то нужно будет добавить ещё и третью ветку.
Мне кажется, PHP следует развивать класс ArrayObject, который уже присуствует в языке и делать прозрачное преобразование примитивных типов в объекты (как это происходит в JavaScript), это решило бы некоторые проблемы.
Спасибо за обзор.
Единственное, что удивило — это нетрадиционность кода в islice:
for ($i = $start; $i > 0 && each($arr); $i--);
for ($i = $stop — $start + 1; $i > 0 && list(,$value) = each($arr); $i--) {
yield $value;
}
Счетчик уменьшается, номер выбираемого элемента увеличивается. Наверное brainfuck повлиял :-)
Комментарий для Karsonito:
А в чём тут нетрадиционность и откуда здесь какие-то традиции? Например, во втором цикле, если я сделаю $i < $stop — $start + 1, то значение будет вычисляться на каждой итерации, а мне этого не нужно, зачем мне лишние вычисления? Лишнюю переменную тоже не хочется делать.
А первый цикл сделан единообразно со вторым.
Так вот откуда ноги растут. Действительно имеет смысл.
А традиции просты — отлаживать проще. Во всяком случае мне. Наверное память не тренированная.
интерестно спс
День добрый!
А объясните нубу, в чем смысл строчки:
function ifilter(callable $predicate = null, $arr) ?
каким образом может быть не передан первый параметр (callable $predicate) и передан второй?
Комментарий для Алексей:
Это позволяет захинтовать первый параметр так, чтобы вместо callable можно было передать null.