Group Abstract Group Abstract

Message Boards Message Boards

0
|
10.9K Views
|
11 Replies
|
12 Total Likes
View groups...
Share
Share this post:

[?] Different ways to define a function?

Posted 7 years ago
POSTED BY: Edward Davis
11 Replies

The major use (that I see at least) for anonymous functions using the & notation is to introduce functions within expressions instead of declaring them on their own. For example, maybe you have a list of numbers and want to turn it into a list where each number is replaced with the result of some predicate (like >4). You could do this by using an anonymous functions like this:

In[1]:= # > 4 & /@ {1, 2, 3, 4, 5, 6}

Out[1]= {False, False, False, False, True, True}

This idiom is common for things like mapping a function over a list, or defining a predicate used by other functions like Select:

In[2]:= Select[{1, 2, 3, 4, 5, 6}, # > 4 &]

Out[2]= {5, 6}

In both of these cases the function being defined just has one parameter, and is equivalent to this function using the other notation that you mentioned:

In[3]:= f[x_] := x > 4

In[4]:= Select[{1, 2, 3, 4, 5, 6}, f]

Out[4]= {5, 6}

The advantage of defining a named function like that is that you can use it over and over without restating the body of the function. Anonymous functions are useful when the function serves some transient purpose where you don't want to declare it as a named thing.

This idiom is common in functional languages: the concept of introducing anonymous (or pure) functions is used all over the place in languages that support what are usually called lambdas. If you look around at, say, OCaml or Haskell or F#, you'll see the term lambda used in a very similar manner as pure functions in Mathematica.

I don't believe it's correct to think of the & symbol as "anding" the body of the function with its arguments. The symbol is there more or less to tell the system where the declaration of the function ends. For example, I can say this:

In[1]:= (#^2 & /@ #) &[{1, 2, 3, 4}]

Out[1]= {1, 4, 9, 16}

In that case there are two anonymous functions being defined in different places: the innermost one defines the function being mapped over some list that squares the elements. That is then wrapped up inside another function where the argument is the list to map over. So, the position of the & really is more of a delimiter indicating where the functions are defined. It's a little easier to see this when you look at things in TreeForm:

(#^2 & /@ #) & // TreeForm

Tree Form

I doubt there is a performance difference, but that would require some more knowledge of the internals of how the interpreter handles functions.

POSTED BY: Matthew Sottile

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.

POSTED BY: EDITORIAL BOARD
Anonymous User
Anonymous User
Posted 7 years ago

I will read it. Thanks.

POSTED BY: Anonymous User
POSTED BY: Raspi Rascal

One can set attributes on pure functions no problem. Never mind since the op seems gone.

POSTED BY: Raspi Rascal
Anonymous User
Anonymous User
Posted 7 years ago
POSTED BY: Anonymous User

The important point that one learns while studying this ubiquitous construct is that there are instances where a pure function must be used and that there are even instances where only one form of the pure function, typically the Function[ ] form, can be used. Those instances are rare .. but not as rare as a beginner would not encounter them. When i saw such examples, that was a very enlightening experience! I learned that, in most cases, one can use f[x], Function[ ], or #& with the same effect (result), and you can let your mood decide. But then there are those rare or few cases where no room for choice or decision is left.

i have no examples at hand. i am just saying. and sooner or later the student will encounter such cases too. watch out for them good luck!

POSTED BY: Raspi Rascal

It's often convenient to use a pure function when you start with a function f of two arguments and want to fix one of the arguments so as to evaluate f for several values of the other argument, and then you would combine the pure function with a Map (or, often, the /@ special input form for Map).

For example:

 cubeRootsUnity = Exp[(2 Range[3] \[Pi] I)/3];
 Graphics[{
   Red, PointSize[Large],
   Point /@ ReIm[cubeRootsUnity],
   Blue, Thick, Arrowheads[Medium],
   Arrow[{{0, 0}, #}] & /@ ReIm[cubeRootsUnity]
   }, Axes -> True, PlotRange -> 1.25]

Plot of cube roots of unity

To be utterly precise here, the expressionf[arg1, arg2] is Arrow[{arg1,arg2}], and the fixed first argument is {0, 0}.

It would be tedious (especially if you considered, say, 8th roots of unity instead of cube roots), to repeat the Arrow expression for each individual root.

(Note that Arrow[{{0, 0},#}]& is not Listable.)

POSTED BY: Murray Eisenberg

In the simplest situations the two paradigms are interchangeable. The & way (cryptic slang for Function) may be a little faster for purely numerical functions, because it skips the pattern matching, but I don't really know. The p[x_]= way is much more customisable with all the pattern-matching capabilities. With the & way you don't need to give the function a name, you simply insert the function code into your expressions.

A general-purpose beginner shouldn't be bothered with the &, I think.

POSTED BY: Gianluca Gorni
Anonymous User
Anonymous User
Posted 7 years ago

I appreciate the info.

POSTED BY: Anonymous User
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard