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:
> [(i,j) | i <- [1..3], j <- [1..3]]
[(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)]
> do {i <- [1..3]; j <- [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:
> sequence [getLine, getLine, getLine]
one
two
three
["one","two","three"]
Consequently, using sequence
with the list monad gives you combinations for free:
> do {i <- [1..3]; j <- [1..3]; return [i,j]}
[[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[3,2],[3,3]]
> 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 >>= k = concat (map k m)
# return x = [x]
# fail s = []
function listMonad()
{
return (object) array(
'bind' => function($m, $k) {
return call_user_func_array('array_merge', array_map($k, $m));
},
'return' => function($x) { return array($x); },
'fail' => function($s) { return array(); }
);
}
# sequence :: Monad m => [m a] -> m [a]
# sequence = foldr mcons (return [])
# where mcons p q = p >>= \x -> q >>= \y -> return (x:y)
function sequence($monad, $list)
{
$mcons =
function($p, $q) use ($monad) {
return call_user_func($monad->bind, $p,
function($x) use ($monad, $q) {
return call_user_func($monad->bind, $q,
function($y) use ($monad, $x) {
return call_user_func($monad->return, cons($x, $y));});});};
return foldr($mcons, call_user_func($monad->return, array()), $list);
}
# foldr :: (a -> b -> b) -> b -> [a] -> 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]]
Note that closures were introduced in PHP 5.3.0 . – Joey Adams – 2011-04-04T21:30:17.630
I wasn't implying it was breaking news ^^ – cbrandolino – 2011-04-04T21:50:50.763
2After all the pessimists and nihilists, now we have wannabilists! – Timwi – 2011-04-05T16:53:28.930