MathGroup Archive 1998

[Date Index] [Thread Index] [Author Index]

Search the Archive

Re: efficiency of Function



> The following test shows that Function that uses Slot as its formal
> parameters are significantly faster than explicit naming of its formal
> parameters. i.e. Function[Slot[1]^2] is faster than Function[{x},x^2].


> My question is: why the speed difference stick with the function? I
> assume that functions with identical behaviors should be identical
> internally too. (theoretically, of course)
> 
> Is this something we can expect improvement, or are there situations
> where f1 and f2 behaves differently? (or are there subtle situations
> where constructs using Slot and explicit naming can effect the meaning
> of the function?)


Xah,

Function does behave differently when its variables are named Symbol
variables instead of #n and ##n.  The difference in meaning applies
when the body of the function contains other scoping constructs
(Function, Module, Rule, Set, Condition, et al).

When Function's variables are named Symbols

  Function[{v1, ..., vn}, body]

it is more careful to respect other scoping constructs that may be
inside 'body'.  Like Module and With, Function respects the local
variables and bodies of nested scoping constructs in two ways:

  (1) When the outer and inner scoping construct both declare 'x' local,
      the outer will not go into the inner's body and tag or replace
      occurrences of 'x'.  The inner construct owns all the variables
that
      it declares local.

  (2) When the outer construct declares 'x' local but the inner
construct
      doesn't, then if 'x' occurs in the body of the inner construct,
the
      outer will rename all the inner's local variables (using the
      var -> var$ scheme).  This is because if the outer construct owns
any
      variables in the inner's body, then it's possible that by
      coincidence, the outer will substitute values into the inner that
      happen to be the same as the local symbols of the inner.

For both of these, the inner construct must be well-formed in some
sense, which means it must at least have the correct number of
arguments.  A Function with eight arguments isn't well-formed and won't
be respected.

A "respectful" scoping construct bears the burden of scanning its entire
body to decide which instances of its declared symbols it owns --and to
tag them as such, and which it does not own.  This takes time, and if
renaming must be applied to inner constructs to protect them, it takes
more time.


When Function has just one argument, meaning its variables are Slot #n
and SlotSequence ##n expressions, then it doesn't respect nested
scoping constructs.  It plows right on through everything except other
Function's of one argument.  When it encounters a Function of one
argument, it can simply skip that entire subexpression, since all #n
and ##n in there will be owned by something else.  Also, all
one-argument Function use the same variables, #n and ##n, so there is
no renaming to do.  Slot variables always belong to the nearest
enclosing one-argument Function, but no other scoping construct can use
Slot variables (actually, Compile can, but you shouldn't take advantage
of that).

Since a one-argument Function has no restrictions on its handling of the
body other than "don't descend into one-argument Function
subexpressions", it's easier for it to decide what it owns in the body,
and consequently, it's faster.  If speed is your ultimate concern, and
you don't need Module-style scoping behavior, then pure functions with
# variables are faster than With[vars, body] and Function[vars, body].


Here are some examples to exhibit the scoping behaviors.


The outer Function won't overwrite the inner Function's x with 0:

In[16]:= Function[x, Function[x, x + 1]][0]

Out[16]= Function[x, x + 1]


The outer Function owns x inside the body of the inner Function, so it
renames the inner Function's variables as a precaution:

In[17]:= Function[x, Function[a, a + x]][0]

Out[17]= Function[a$, a$ + 0]


The following illustrates the motivation for automatic renaming of
variables:

In[19]:= Function[x, Function[u, u + x]][u]

Out[19]= Function[u$, u$ + u]

If renaming didn't occur, the result would have been Function[a, a + a].
The inner's u has become confused with a happenstance value of u
inserted by the outer.  This would violate the principle that local
variables are dummy variables whose names are immaterial.


HoldForm can be used to freeze the body, to see how the outer scoping
construct has affected its form:

In[20]:= Function[x, HoldForm @ Module[{u = 2}, u + x]][u]

Out[20]= Module[{u$ = 2}, u$ + u]

In[22]:= Module[{x = u}, HoldForm @ With[{u = 1}, u + x]]

Out[22]= With[{u$ = 1}, u$ + x$8]


Finally, here's an example which demonstrates that although the inner's
local variables themselves are protected, their _initial values_ are
computed in the outside scope:

In[33]:= Function[x, HoldForm @ Module[{x = x^2 + 1}, {x}]] [2]

                      2
Out[33]= Module[{x = 2  + 1}, {x}]

In[34]:= ReleaseHold[%]

Out[34]= {5}


Robby Villegas



  • Prev by Date: All notebook cells open
  • Next by Date: Re: Question about Mathematica
  • Prev by thread: efficiency of Function
  • Next by thread: few easy questions