Pull to refresh

Comments 6

Интересно узнать насчёт yield — какие плюсы/удобства получились при его применении? Стал ли код библиотеки компактнее или выросла производительность? Какие были проблемы при переписывании на yield?
Код изменился радикально. Из набора костылей и подпорок вот такого нечитаемого вида:

return new Enumerable(function () use ($self, $inner, $outerKeySelector, $innerKeySelector, $resultSelectorValue, $resultSelectorKey)
{
    /** @var $self Enumerable */
    /** @var $inner Enumerable */
    /** @var $arrIn array */
    $itOut = $self->getIterator();
    $itOut->rewind();
    $lookup = $inner->toLookup($innerKeySelector);
    $arrIn = null;
    $posIn = 0;
    $key = null;

    return new Enumerator(function ($yield) use ($itOut, $lookup, &$arrIn, &$posIn, &$key, $outerKeySelector, $resultSelectorValue, $resultSelectorKey)
    {
        /** @var $itOut \Iterator */
        /** @var $lookup \YaLinqo\collections\Lookup */
        while ($arrIn === null || $posIn >= count($arrIn)) {
            if ($arrIn !== null)
                $itOut->next();
            if (!$itOut->valid())
                return false;
            $key = call_user_func($outerKeySelector, $itOut->current(), $itOut->key());
            $arrIn = $lookup[$key];
            $posIn = 0;
        }
        $args = array($itOut->current(), $arrIn[$posIn], $key);
        $yield(call_user_func_array($resultSelectorValue, $args), call_user_func_array($resultSelectorKey, $args));
        $posIn++;
        return true;
    });
});

код стал вот такой конфеткой:

return new Enumerable(function () use ($inner, $outerKeySelector, $innerKeySelector, $resultSelectorValue, $resultSelectorKey) {
    $lookup = $inner->toLookup($innerKeySelector);
    foreach ($this as $ok => $ov) {
        $key = $outerKeySelector($ov, $ok);
        if (isset($lookup[$key]))
            foreach ($lookup[$key] as $iv)
                yield $resultSelectorKey($ov, $iv, $key) => $resultSelectorValue($ov, $iv, $key);
    }
});

В принципе и колбэк лишний можно убрать, но я пока не определился: можно ли полагаться на то, что итератор не будет выполнять дорогие операции в конструкторе?

Скорость не мерял, но с точки зрения количества вызовов мой код был близок к Ginq, то есть скорость возрасла раза в два-три. Когда в язык добавляли yield, тормознутость реализации через итераторы была одним из аргументов (кроме основного — читаемости кода).

Проблем не было, была радость от удаления такого количества мусора. :) Ну и со стопроцентным покрытием тестами нервничать особо не приходилось.

Заодно выпилил к чёртовой бабушке все «коллекции» и прочий хлам и заменил на обычные массивы. Потерялась возможность класть объекты и массивы в ключи, но PHP-программистам такая идея в принципе чужда, поэтому невелика потеря.
UFO just landed and posted this here
Это одна из самых тяжеловесных функций со сложной логикой. Остальные функции в большинстве своём гораздо проще. Ну вот where в полном составе, например:

public function where ($predicate)
{
    $predicate = Utils::createLambda($predicate, 'v,k');
    return new Enumerable(function () use ($predicate) {
        foreach ($this as $k => $v)
            if ($predicate($v, $k))
                yield $k => $v;
    });
}

(Строчку return new Enumerable(function () use ($predicate) в принципе можно выкинуть. Это наследие от первой версии, надо убедиться, что это не сломает какую-то логику.)

Ну и один из источников сложностей — это что в одной функции приходится рассматривать все варианты, в то время как во многих других языках есть перегрузки методов.
Спасибо за развёрнутый ответ, наглядно!

Возник ещё один теоретический вопрос по генераторам. В PHP 7 планируется поддержка «return» внутри функции-генератора (https://wiki.php.net/rfc/generator-return-expressions) — это как-нибудь пригодится? Видите для себя область применения return?
Мне не повредили бы лямбды (тут всё ясно), extension-методы (чтобы оставить чистые итераторы вместо обёрток), какой-то способ семантично возвращать ключ и значение не виде массива или итератора (чтобы упростить маловменяемые сигнатуры)…

Возвращаемые значения у генераторов — это, по сути, генерация значения + итератора. Единственное место, где такая сущность имеет смысл — это GroupBy, но там нужна независимость значений в паре, поэтому фича не подойдёт даже для упрощения кода на пару строчек.

Судя по спеке, эта штука нужна для сопрограмм в общем виде, а в частном случае генераторов последовательностей пользы мало.
Sign up to leave a comment.

Articles