"list comprehension" in php. PHP has closures, let's play around with them.

I have some arrays similar to, $wannabelist = array (1, 2, 3, 4, 5, 6).

I want to do this: compr( expression($params), $wannabelist, $wannabilist2 ...).

compr() should do the following:


Take all the possible combinations of elements of the arrays given as parameters;
Pass each of them to the expression passed as first parameter;
Yield the result.


A particularly satisfying function will:


Avoid changing the value of any variable;
Be elegant;
Contain an inordinate amount of nested parenthesis/brackets;
Avoid foreach() loops.
First, a few neat things about list comprehensions, from a Haskell perspective:


The list monad in Haskell is set up so any list comprehension can be transformed into do notation rather easily:

&gt; [(i,j) | i &lt;- [1..3], j &lt;- [1..3]]
[(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)]
&gt; do {i &lt;- [1..3]; j &lt;- [1..3]; return (i,j)}
[(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)]

Haskell has a sequence function that takes a list of "actions" in a monad and returns a list of the results:

&gt; sequence [getLine, getLine, getLine]
one
two
three
["one","two","three"]

Consequently, using sequence with the list monad gives you combinations for free:

&gt; do {i &lt;- [1..3]; j &lt;- [1..3]; return [i,j]}
[[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[3,2],[3,3]]
&gt; sequence [[1..3], [1..3]]
[[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[3,2],[3,3]]



Hence, the sequence function is very similar to the compr function asked for.  It just needs to be wrapped and mapped a bit:

function compr()
{
    $args = func_get_args();
    $f = $args[0];
    $call_f = function($array) use ($f) {
        return call_user_func_array($f, $array);
    };
    return array_map($call_f, sequence(listMonad(), array_slice($args, 1)));
}


Now, without further ado, here is a nearly direct translation of the list monad and sequence function from Haskell to PHP:

# instance  Monad []  where
#     m &gt;&gt;= k          = concat (map k m)
#     return x         = [x]
#     fail s           = []
function listMonad()
{
    return (object) array(
        'bind'   =&gt; function($m, $k) {
            return call_user_func_array('array_merge', array_map($k, $m));
        },
        'return' =&gt; function($x) { return array($x); },
        'fail'   =&gt; function($s) { return array(); }
    );
}

# sequence       :: Monad m =&gt; [m a] -&gt; m [a] 
# sequence       =  foldr mcons (return [])
#                     where mcons p q = p &gt;&gt;= \x -&gt; q &gt;&gt;= \y -&gt; return (x:y)
function sequence($monad, $list)
{
    $mcons =
        function($p, $q) use ($monad) {
            return call_user_func($monad-&gt;bind, $p,
                function($x) use ($monad, $q) {
                    return call_user_func($monad-&gt;bind, $q,
                        function($y) use ($monad, $x) {
                            return call_user_func($monad-&gt;return, cons($x, $y));});});};
    return foldr($mcons, call_user_func($monad-&gt;return, array()), $list);
}

# foldr            :: (a -&gt; b -&gt; b) -&gt; b -&gt; [a] -&gt; b
# foldr f z []     =  z
# foldr f z (x:xs) =  f x (foldr f z xs)
function foldr($f, $z, $xs)
{
    if (empty($xs))
        return $z;
    return call_user_func($f, $xs[0], foldr($f, $z, array_slice($xs, 1)));
}

function cons($x, $xs)
{
    return array_merge(array($x), $xs);
}


I believe this meets all four of your criteria, especially the "inordinate amount of nested parenthesis/brackets" part.

Test code:

function printList($show, $list)
{
    echo showList($show, $list) . "\n";
}

function showList($show, $list)
{
    $str = "[";
    foreach($list as $x) {
        if ($str !== "[")
            $str .= ",";
        $str .= call_user_func($show, $x);
    }
    return $str . "]";
}

# Count in binary
printList('strval',
    compr(function($a, $b, $c, $d) {
        return "$a$b$c$d";
    }, array(0,1), array(0,1), array(0,1), array(0,1)));

# Combinations of items
printList(function($a) {return showList('strval', $a);},
    compr(function($a, $b, $c) {
        return array($a, $b, $c);
    }, array(1, 2), array(3, 4, 5), array(6, 7, 8, 9)));


Output:

[0000,0001,0010,0011,0100,0101,0110,0111,1000,1001,1010,1011,1100,1101,1110,1111]
[[1,3,6],[1,3,7],[1,3,8],[1,3,9],[1,4,6],[1,4,7],[1,4,8],[1,4,9],[1,5,6],[1,5,7],[1,5,8],[1,5,9],[2,3,6],[2,3,7],[2,3,8],[2,3,9],[2,4,6],[2,4,7],[2,4,8],[2,4,9],[2,5,6],[2,5,7],[2,5,8],[2,5,9]]