8
$\begingroup$

I encountered unexpected difference in variable renaming behavior depending on whether a pure function with a named parameter, or a pure function with a slot is used:

{x_, y_, z_} :> # & /@ {{y, z, x}, {z, x, y}}
(* {{x_, y_, z_} :> {y, z, x}, {x_, y_, z_} :> {z, x, y}} *)

Function[s, {x_, y_, z_} :> s] /@ {{y, z, x}, {z, x, y}}
(* {{x$_, y$_, z$_} :> {y, z, x}, {x$_, y$_, z$_} :> {z, x, y}} *)

Is there an explanation for this?


Update: It was suggested that this question is a duplicate of another, already answered question. I read that question and answers, but still do not understand why is it significant whether the function parameter is named or unnamed? There is nothing in conflict with its name s. The conflict happens for the names x, y, z, and it happens regardless whether we use a named or unnamed parameter. So this question is still open for me.

$\endgroup$
  • 4
    $\begingroup$ Why is that unexpected? Variables in Pure Functions and Rules $\endgroup$ – Karsten 7. Aug 21 '16 at 3:26
  • 4
    $\begingroup$ The second pure function has a named formal parameter (s) localized to that pure function and the first pure function has an unnamed formal parameter (#, and therefore no localization). $\endgroup$ – Karsten 7. Aug 21 '16 at 6:15
  • 1
    $\begingroup$ @Karsten7. This difference is not considered in the referenced Documentation article, so the question is perfectly legitimate and deserves a good answer. $\endgroup$ – Alexey Popkov Aug 21 '16 at 7:43
  • 3
    $\begingroup$ Related: (20766) $\endgroup$ – Mr.Wizard Aug 21 '16 at 8:49
  • 1
    $\begingroup$ "The conflict happens for the names x, y, z, ..." Nope, compare Function[s, {x_, y_, z_} :> s] /@ {{a, b, c}, {d, e, f}}. It's about RuleDelayed being inside a scoping construct (Function[{x,…},body]) or a non-scoping one (Function[body]). $\endgroup$ – Karsten 7. Aug 21 '16 at 21:27
8
$\begingroup$

I believe that it is better to start with the second part of the question, and only then move on to the first part.

There is nothing in conflict with its name s. The conflict happens for the names x, y, z, and it happens regardless whether we use a named or unnamed parameter. So this question is still open for me.

Actually there is no scoping conflict which involves names x, y, z. We can simplify your example and get the same behavior:

Function[s, {x_, y_, z_} :> s]@d
{x$_, y$_, z$_} :> d

As you see, variables are renamed despite the fact that they aren't used anywhere except to RuleDelayed inside of Function. So there is no conflict but rather this is how nested lexical scoping implemented in Mathematica. We can replace RuleDelayed with Function in order to emphasize this point:

Function[s, Function[{x, y, z}, s]]@d
Function[{x$, y$, z$}, d]

... or replace Function with RuleDelayed:

d /. RuleDelayed[s_, RuleDelayed[{x_, y_, z_}, s]]
{x$_, y$_, z$_} :> d

... or we can try the same construct with With:

With[{s = d}, With[{x, y, z}, s]]

With::lvws: Variable x$ in local variable specification {x$,y$,z$} requires a value. >>

With[{x$, y$, z$}, d]

Here the inner With isn't evaluated because of incorrect semantics but the variables are already renamed by the outer With! Again, there is no scoping conflict.

It is worth to stress that the outer lexical scoping construct renames the parameters of the inner before the latter is evaluated in any sense. We can make the inner construct completely inert by wrapping it with HoldComplete in order to emphasize this point:

With[{s = d}, HoldComplete@With[{x, y, z}, s]]
HoldComplete[With[{x$, y$, z$}, d]]
Module[{s}, HoldComplete@Module[{x, y, z}, s]]
HoldComplete[Module[{x$, y$, z$}, s$603]]

For non-lexical scoping constructs the renaming of variables isn't performed:

With[{s = d}, HoldComplete@Block[{x, y, z}, s]]
HoldComplete[Block[{x, y, z}, d]]
With[{s = d}, HoldComplete@Internal`InheritedBlock[{x, y, z}, s]]
HoldComplete[Internal`InheritedBlock[{x, y, z}, d]]

... and non-lexical scoping constructs themselves do not rename variables in inner scoping constructs (regardless whether the latter are lexical or not):

Block[{s = d}, Rule[{x, y, z}, s]]
{x, y, z} -> d
Block[{s = d}, Hold@Module[{x, y, z}, s]]
Hold[Module[{x, y, z}, s]]
Block[{s = d}, Hold@Block[{x, y, z}, s]]
Hold[Block[{x, y, z}, s]]

The necessary condition for renaming of variables of the inner scoping construct is that its body is modified by the outer scoping construct. For example, in the following examples lexical replacement doesn't change the body, and that's why no renaming is performed:

Function[x, Function[y, x]][x]
Function[y, x]
With[{x = x}, Function[{y, z}, x]]
Function[{y, z}, x]

Excepting Rule and RuleDelayed (see below), the outer scoping constructs respect the inner lexical scope:

Function[x, Function[x, x]][y]
Function[x, x]
Function[{x, y}, Function[x, x + y]][a, b]
Function[x$, x$ + b]
With[{x = a, y = b}, Function[x, x + y]]
Function[x$, x$ + b]

The only exceptions are Rule and RuleDelayed which in the situations of direct conflict with enclosed scoping construct break the scope of the latter and perform lexical replacement without renaming of conflicting variables:

(* body isn't modified *)

y /. RuleDelayed[x_, Function[{x, z}, y + z]]
Function[{y, z}, y + z]
(* body is modified *)

y /. RuleDelayed[x_, Function[{x, z}, x + y + z]]
Function[{y, z$}, y + y + z$]
y /. RuleDelayed[x_, RuleDelayed[{x_, z_}, x + y + z]]

RuleDelayed::rhs: Pattern x_ appears on the right-hand side of rule x_:>{x_,z_}:>x+y+z.

{y_, z$_} :> y + y + z$
y /. RuleDelayed[x_, Hold@Module[{x, z}, x + y + z]]
Hold[Module[{y, z$}, y + y + z$]]
(* modified body of non-lexical scoping construct *)

y /. RuleDelayed[x_, Hold@Block[{x, z}, x + y + z]]
Hold[Block[{y, z}, y + y + z]]

Now we can move to the first part of the question: why completely anonymous pure functions (the form Function[body] or body &) do not rename variables in inner scoping constructs?

The answer is that they ... just implemented this way :). Outer scoping constructs can't rename unnamed parameters of completely anonymous pure functions, and anonymous pure functions themselves do not rename variables in other scoping constructs. In this respect they are unique in the Wolfram Language and exactly this feature allows transparent building of new scoping constructs at the run-time:

expr = x^2;
(y[x_, z_] := #) &@expr;
Definition[y]
y[x_, z_] := x^2
RuleDelayed[{x_, z_}, ## + y + z] &[x, y]
{x_, z_} :> x + y + y + z

What can we find in the Documentation on the subject?

The above case with nested Functions is covered in the Documentation explicitly:

Here is a nested pure function.

Function[{x}, Function[{y}, x + y]]
Function[{x}, Function[{y}, x + y]]

The Wolfram Language renames the formal parameter y in the inner function to avoid conflict with the global object y.

%[2 y]
Function[{y$}, 2 y + y$]

The resulting pure function behaves as it should.

%[a]
a + 2 y

And at the next line (emphasis is mine):

In general, the Wolfram Language renames the formal parameters in an object like Function[vars,body] whenever body is modified in any way by the action of another pure function.

I wish to stress the word like: from the examples in the previous section it should be clear that lexical scoping constructs Function, RuleDelayed, With work very similarly and are interchangeable. So the Documentation clearly states that the same rule is applied to any of these constructs in a situation of nested scoping.

I recommend to read the referenced Documentation page carefully up to the end. Here I'll emphasize a paragraph that should ultimately answer your question (emphasis is mine):

The Wolfram Language renames formal parameters in pure functions more liberally than is strictly necessary. In principle, renaming could be avoided if the names of the formal parameters in a particular function do not actually conflict with parts of expressions substituted into the body of the pure function. For uniformity, however, the Wolfram Language still renames formal parameters even in such cases.

P.S. I must stress that every mentioning of pure function in this section (and also on the linked Documentation page) should be read as pure function with named arguments and shouldn't be applied to anonymous pure functions (without named arguments). The latter are very special in the WL: they do not interfere with symbols (or named parameters) in any way. For example they change the body of Function without renaming its parameters what allows to break scoping, compare:

Function[{y}, #] &[y]
Function[{y}, y]
Function[{x}, Function[{y}, x]][y]
Function[{y$}, y]
$\endgroup$
  • $\begingroup$ Thanks. What are the grounds for your claim that Function[body] is non-scoped? Is it explicitly mentioned in the documentation? $\endgroup$ – Vladimir Reshetnikov Aug 22 '16 at 20:05
  • $\begingroup$ I have spent some time searching in the Documentation but haven't found an explicit statement. But it is mentioned in the book by @LeonidShifrin. I updated the answer with this information and added some rationalization. $\endgroup$ – Alexey Popkov Aug 23 '16 at 7:47
  • $\begingroup$ @Vladimir Finally I removed that statement from the answer. As a result of more detailed acquaintance with definition of the term "scoping" and experimenting I come to the conclusion that Function[body] is indeed a lexical scoping construct. And @LeonidShifrin agreed with me that Function[body] is a scoping construct (just very limited in its capabilities as compared to others). $\endgroup$ – Alexey Popkov Mar 16 '17 at 20:59
4
$\begingroup$

Adapting Rojo's approach in Using With to scope over pure functions:

Function[s, Identity[RuleDelayed][{x_, y_, z_}, s]] /@ {{y, z, x}, {z, x, y}}
(*  {{x_, y_, z_} :> {y, z, x}, {x_, y_, z_} :> {z, x, y}}  *)
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.