Skip to content

Instantly share code, notes, and snippets.

@morrisonlevi
Last active September 4, 2015 15:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save morrisonlevi/8323125182ca3b5e2124 to your computer and use it in GitHub Desktop.
Save morrisonlevi/8323125182ca3b5e2124 to your computer and use it in GitHub Desktop.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment