# How can one write a robust ListableQ function?

I would like to have a test that determines if a particular function is Listable. In the case of Symbols this is merely a matter of checking Attributes. Function definitions with the Listable attribute are a bit more involved but quite easy.

However I specifically want to test for inherent listability in as many cases as possible.

For example consider the function from Case #4 in Alternatives to procedural loops and iterating over lists in Mathematica:

(3 - #)/(7 * #) &


This function is inherently listable:

fn = (3 - #)/(7*#) &;
Map[fn, {1, 2, 3}]
fn @ {1, 2, 3}

{2/7, 1/14, 0}
{2/7, 1/14, 0}

• One must consider Functions with multiple arguments, both the Slot and named parameter type.

• Ideally the test would handle pattern-based (DownValues) functions to the extent that is possible.

-
What if your list is {3, 4, {1,2}}? Then, listable f, gives {f[3], f[4], {f[1], f[2]}}. So, while (3 - #)/(7 #)& is inherently listable, I don't think it is completely listable. So, ListableQ will have some limitations. – rcollyer Sep 1 '13 at 14:34
@rcollyer / is a listable binary operator so it works out fine since both arguments will have the same shape. – ssch Sep 1 '13 at 15:25
@rcollyer I don't see the problem with your example. Perhaps you have another one? – Mr.Wizard Sep 1 '13 at 22:38
@Mr.Wizard initially it was very clear to me when I wrote, not so much now, especially with ssch's comment. I need to see if I can recover what I intended, but my mental tape drive is faulty. :P – rcollyer Sep 3 '13 at 12:45
@rcollyer I know that feeling. Too well. – Mr.Wizard Sep 3 '13 at 13:25

This is what I have so far. It's far from complete, but still a good start I think.

listableQ[s_Symbol] /; MemberQ[Attributes @ s, Listable] := True
listableQ[s_Symbol] := MatchQ[DownValues[s], {__?test2}]

listableQ[Verbatim[Function][_, _, attr_]] /; ! FreeQ[{attr}, Listable] := True
listableQ[Verbatim[Function][vars_, body_, ___]] := test1[vars, body]
listableQ[body_ &] :=
Cases[Unevaluated[body], _Slot, {0, -1}, Heads -> True] /. x_ :> test1[x, body]

SetAttributes[test1, HoldAll];
test1[vars_, body_] :=
Cases[Unevaluated[body],
h_[args___] /; ! FreeQ[Unevaluated[{args}], Alternatives @@@ HoldPattern[vars]] :> h,
{0, -1}
] ~MatchQ~ {__?listableQ}

test2[lhs_ :> rhs_] :=
Union @@ Cases[Unevaluated[lhs],
Verbatim[Pattern][p_, _] :> Hold[p],
] /. x_ :> test1[x, rhs]


Here is a very basic test; qq represents a generic function that isn't listable.

ff[a_, b__, c : _List] := a^2/Sign[b^2 + c] + qq[z]
f2[a_, b__, c : _List] := qq[a, b^2] + c

listableQ /@ {
(3 - #)/(7*#) &,
(3 - #)/(qq[7]*#) &,
qq[(3 - #), (7*#)] &,
Function[x, x + qq[5] + x/2],
Function[x, qq[x + 5, x/2]],
Function[, qq[(3 - #), (7*#)], Listable],
ff,
f2
}


{True, True, False, True, False, True, True, False}

-
I am aware that there are ways that this fails, and moreover, that the question itself may be hard to answer in a complete way. For example, if qq[7] were to return {1, 2} this would break listability of second function. Nevertheless I am interested in exploring and improving this kind of test. – Mr.Wizard Sep 1 '13 at 13:36
Additionally I realize that my ff and f2 functions are poorly conceived; they were hasty examples. I'll revisit this tomorrow. – Mr.Wizard Sep 1 '13 at 14:02

The naive one-argument test:

naiveListableQ[f_] := f[{1, 2}] === f /@ {1, 2}


Cons:

• Evaluates the function
• Can give incorrect result
• {1,2} has to be in domain of function
• ...

Results:

naiveListableQ /@ {
(3 - #)/(7*#) &,
(3 - #)/(qq[7]*#) &,
qq[(3 - #), (7*#)] &,
Function[x, x + qq[5] + x/2],
Function[x, qq[x + 5, x/2]],
Function[, qq[(3 - #), (7*#)], Listable],
}


{True, True, False, True, False, True}

-
I considered this approach and decided to go a different direction. It may be the most rigorous approach for anonymous functions that don't contain pattern matching. However, I don't like the fact that it does evaluate the function (multiple times) which works against the applications I have in mind. – Mr.Wizard Sep 1 '13 at 13:54
@Mr.Wizard CW was intentional as it was bit too long for a comment. Except for the simplicity of it I don't like it either :) – ssch Sep 1 '13 at 13:59