(This post is fairly lengthy so I have copied it into a gist if you
would prefer to read it that way:
https://gist.github.com/morrisonlevi/8323125182ca3b5e2124)

Here's a practical example of chaining closures together that allows
us to easily implement versions of `array_map` and `array_reduce` that
are highly generic, meaning they can be used with various kinds of
input and output types instead of just arrays. These new versions can
also easily be chained together to form a "pipe" of transformations.
While the short-form closures are not required for this to work it
does make it much simpler.

Also, I know this is a fair bit of code to dump at once; it's really
hard to give meaningful examples that don't have some length to them.
Please don't skip this code!

    $map = $fn ~> $input ~> {
        foreach ($input as $key => $value) {
            yield $key => $fn($value);
        }
    };

    $reduce = $initial ~> $fn ~> $input ~> {
        $accumulator = $initial;
        foreach ($input as $value) {
            $accumulator = $fn($accumulator, $value);
        }
        return $accumulator;
    };

    // this is just a helper function
    // can skip reading it if you want
    function chain(callable $fn, callable ...$callables) {
        $functions = func_get_args();
        return (...$params) ~> {
            $count = count($functions);
            $carry = $functions[0](...$params);
            for ($i = 1; $i < $count; $i++) {
                $carry = $functions[$i]($carry);
            }
            return $carry;
        };
    }

This essentially allows for binding only certain parameters and
chaining whole algorithms together:

    $algorithm = chain(
        $map($x ~> $x * 2),
        $reduce([])(function ($accumulator, $value) {
            $accumulator[] = $value;
            return $accumulator;
        })
    );

    $result = $algorithm($iterable);

Note that while I've used a basic array here for brevity I could have
used any kind of container that supports adding single items. For
instance, I could have used a set:

    $set = new Set();
    $algorithm = chain(
        $map($x ~> $x * 2),
        $reduce($set)(function ($set, $value) {
            $set->add($value)
            return $set;
        })
    );

    $result = $algorithm($iterable);

Now all the duplicates get removed should there be any.

-----

Hopefully I've been able to demonstrate that this style of coding is
powerful and that the chaining of closures was helpful. Here are the
long-form closure equivalents for contrast:

    $map = function ($fn) {
        return function($input) use($fn) {
           foreach ($input as $key => $value) {
               yield $key => $fn($value);
            }
        };
    };

    $reduce = function ($initial) {
        return function ($fn) use($initial) {
            return function($input) use($initial, $fn) {
                $accumulator = $initial;
                foreach ($input as $value) {
                    $accumulator = $fn($accumulator, $value);
                }
                return $accumulator;
            };
        };
    };

    function chain(callable $fn, callable ...$callables) {
        $functions = func_get_args();
        return function(...$params) use($functions) {
            $count = count($functions);
            $carry = $functions[0](...$params);
            for ($i = 1; $i < $count; $i++) {
                $carry = $functions[$i]($carry);
            }
            return $carry;
        };
    }

    $algorithm = chain(
        $map(function ($x) {
            return $x * 2;
        }),
        $reduce([])(function ($accumulator, $value) {
            $accumulator[] = $value;
            return $accumulator;
        })
    );

----

Some people have also suggested removing the block syntax for short
closures. The implementation of reduce as defined above is a
demonstration of why the block syntax is helpful:

    $reduce = $initial ~> $fn ~> $input ~> {
        $accumulator = $initial;
        foreach ($input as $value) {
            $accumulator = $fn($accumulator, $value);
        }
        return $accumulator;
    };

With the block syntax removed the last closure in the chain has to use
long-form like this:

    $reduce = $initial ~> $fn ~> function($input) use($initial, $fn) {
        $accumulator = $initial;
        foreach ($input as $value) {
            $accumulator = $fn($accumulator, $value);
        }
        return $accumulator;
    };

I hope you'll agree with me that this is just weird. I was initially
against the block syntax but after using it in meaningful code I
concluded that the block syntax is helpful.

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to