First use:
While the true beginner need not worry about &
, the experienced programmer who is new to Mathematica might be interested in the following difference in speed:
Clear[f];
f[x_, y_] := x*y;
Table[f[x, y], {x, 1000}, {y, 1000}]; // RepeatedTiming
(* {0.591, Null} *)
Table[#1 #2 &[x, y], {x, 1000}, {y, 1000}]; // RepeatedTiming
(* {0.017, Null} *)
The Function[]
form is just as fast in this case, but not as fast as the pure expression:
Table[Function[{a, b}, a*b][x, y], {x, 1000}, {y, 1000}]; // RepeatedTiming
(* {0.016, Null} *)
Table[x*y, {x, 1000}, {y, 1000}]; // RepeatedTiming
(* {0.012, Null} *)
The reason is that Table
calls Compile
to compile a compilable expression when the table length is long (see SystemOptions["CompileOptions" -> "*CompileLength"]
for "TableCompileLength"
and related parameters). However the named function f[x, y]
is not compilable whereas the anonymous functions #1 #2&[x, y]
and Function[{a, b}, a*b][x, y]
are compilable. If you're wondering why they aren't all equivalent to the compilable expression x * y
, the reason is that the expression in Table
is not evaluated before it is passed to the compiler. The expression f[x, y]
depends on the pattern-matching function f[]
and the compiler runtime environment does not handle pattern matching. (You can get around this, for this example, with Table[Evaluate@f[x, y], {x, 1000}, {y, 1000}]
to pre-evaluate f[x, y]
, which in effect yields Table[x*y, {x, 1000}, {y, 1000}]
.)
One use for anonymous functions is in passing a (simple) function to another routine. The following function mytab
makes the same table as above from a function that is passed to it. We see the same difference is timing.
mytab[f_] := Table[f[x, y], {x, 1000}, {y, 1000}];
mytab[f]; // RepeatedTiming
mytab[#1 #2 &]; // RepeatedTiming
(*
{0.59, Null}
{0.017, Null}
*)
Using Times
as the function is equivalent to the direct expression x * y
above:
mytab[Times]; // RepeatedTiming
(* {0.0124, Null} *)
Aside: I assume x*y
is just a simple test example and not the actual function in use. However, it is an important example. There is an undocumented performance enhancement in Outer
for Times
(and Plus
). The following computed the same table as above almost seven times faster:
Outer[Times, Range@1000, Range@1000]; // RepeatedTiming
(* {0.0018, Null} *)
There is not the same acceleration for other forms, although the anonymous function is faster:
Outer[f, Range@1000, Range@1000]; // RepeatedTiming
Outer[#1 #2 &, Range@1000, Range@1000]; // RepeatedTiming
(*
{0.46, Null}
{0.312, Null}
*)
A second use:
Another way I've used pure functions is to construct a function at run time in terms of #1
, #2
, etc. and then convert it to a function with &
. You can do this in terms of symbols x
and y
and make a function out of the expression you construct. You have to make sure that x
and y
do not have any values assigned while you are constructing the function (which is a solvable problem -- use Block[]
for instance), but &
makes it easier. Simple example:
(* monomial function generator *)
monom[m_, n_] := Module[{expr},
expr = #1^m*#2^n;
Evaluate[expr] &]
monom[1, 1]
(* #1 #2 & *)
A third, minor consideration.
Pure functions are called "anonymous" (I think) because you don't need to name them with a symbol like f
. Thus you don't have to worry about them conflicting with other variables in your or your user's runtime environment.
One needs to balance these considerations with the function parameters (#1
etc.) not having helpful names to remind you what they stand for. One could use Function[{price, quantity}, price * quantity]
instead of #1 #2 &
and sitll use an anonymous function. One place in the docs you find #1 &
etc. is in MeshFunctions. Despite their examples, I tend to use Function[{x, y, z, u, v},...]
for ParametricPlot3D
and so forth, because it helps me make sure I get the formula right. It's hard to read #1 #3 - Sin[#5] &
and other complicated expressions.