15

In recent thread was raised the question: why anonymous pure functions Function[body] (or body &) do not rename symbols in nested scoping constructs while pure functions with named parameters Function[{vars}, body] do rename them as seen from the following example:

lhs_ :> # &@arg
Function[rhs, lhs_ :> rhs]@arg
lhs_ :> arg

lhs$_ :> arg

(in the second case lhs is renamed to lhs$).

The provided explanation (first given in the comment) states that pure function with no named arguments isn't a scoping construct, hence localization of variables in the nested scope isn't performed. This looks as kind of obvious since there is no need to localize variables inside of a construct which doesn't use variables itself (anonymous pure functions use only Slot).

But when trying to find where it is stated in the official Documentation, I was confused: the modern Documentation seems to state the opposite (although all the linked examples are only about the form Function[{vars}, body]), emphasis is mine:

Function constructs can be nested in any way. Each is treated as a scoping construct, with named inner variables being renamed if necessary. »

At the same time Leonid Shifrin notes in his book "Mathematica programming: an advanced introduction" (emphasis is mine):

It is important to note that there is no fundamental difference between functions defined with the # - & notation and functions defined with the Function command, in the sense that both definitions produce pure functions. There are however several technical differences that need to be mentioned.

The first one is that the Function[{vars},body] is a scoping construct, similar to Module, Block, With etc.

what implies that only the form Function[{vars},body] is a scoping construct, not the form defined with the # - & notation.

Let us make the things clear: is the form Function[body] (and equivalent forms body & and Function[Null, body]) a scoping construct or not? I ask both for authoritative references and for rational analysis of the situation.

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368

2 Answers2

8

It seems to me that the Slot form of Function is not a scoping construct as there is no known way to nest one such function inside another with access to the parameters of both functions at the same level. All solutions to that issue involve named parameters.

Since a scoping construct in the context of variable renaming means a structure that can be nested in a fashion that # & cannot, # & is not a scoping construct.

I acknowledge that this argument may be tautological, but in some way the question itself seems tautological: Scoping constructs rename the parameters if inner scoping constructs; Slot Functions do not rename parameters of inner scoping constructs; ergo Slot Functions do not behave like scoping constructs.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • In the expression Function[x, x*#] &[arg] we have access to the parameters of both functions at the same level. Of course it uses named parameter along with anonymous. If not scoping construct, what definition better describes the nature of # &? – Alexey Popkov Aug 23 '16 at 12:20
  • @Alexey A substitution construct perhaps? It is unique within the Mathematica framework; is it necessary to put a particular label on it? "A rose by any other name..." – Mr.Wizard Aug 23 '16 at 12:29
  • 4
    @AlexeyPopkov It is more like a macro. As Mr.Wizard said, scoping construct introduces local scope, that has its domain of visibility in code. The slot parameters are visible from anywhere, which makes them actually placeholders, and makes # - & style of functions essentially macros. The other side of that is that their nesting capabilities are more limited. In your example, you have 2 functions, and you had to use named parameter syntax for one of them precisely because you can't achieve proper nesting using only slots. – Leonid Shifrin Aug 23 '16 at 13:23
  • @LeonidShifrin I'm trying to create a precise and short description of the difference. As I understand there indeed is fundamental difference between the Slot form and the form with named parameters: the former is merely a substitution construct where Slots are simply placeholders, but the latter is a true scoping construct very similar to With (I feel the word "macro" too imprecise since Visual Basic programs in MS Office are also called macroses). Is it correct? – Alexey Popkov Aug 23 '16 at 13:54
  • @LeonidShifrin Also, could you write a canonical answer to this question for easy reference? Recently I come to a confusion due to absence of a good discussion on the topic. – Alexey Popkov Aug 23 '16 at 14:25
  • 5
    +1 I'll note in passing that while it is possible to rewrite Function[x, Function[y, x + y]] as #[#2[1] + #3] &[Function, Slot, #] &, anyone who does so should probably be shot... ducks :) – WReach Aug 23 '16 at 14:44
  • @WReach :) Thank you, it is a great example of tremendous flexibility of the WL. – Alexey Popkov Aug 23 '16 at 15:00
  • @WReach It would be great if you write an answer with an academical interpretation of the issue: your answers from this viewpoint were always noble and this topic is really fundamental. – Alexey Popkov Aug 23 '16 at 15:39
  • @WReach Good point. I did realize that form as well when writing a comment, but didn't want to complicate the matters here. So, my "can't achieve proper nesting" part is not fully correct. The funny thing is that I thought that this small inaccuracy would go unnoticed, but it didn't :). Reminds me to be more careful when making statements on this site. – Leonid Shifrin Aug 23 '16 at 17:53
  • @AlexeyPopkov Re: canonical answer: alas, don't have the time for that at the moment. May be later. But the answer of Mr. Wizard is right on target. Also, if WReach contributes an answer as well, then there will be no need for mine at all. – Leonid Shifrin Aug 23 '16 at 17:57
  • @LeonidShifrin The example provided by WReach also shows that your statement that "the slot parameters are visible from anywhere" isn't correct: in #[#2[1] + #3] &[Function, Slot, #] & the first # isn't bound to the first argument of the outer Function. This again raises my confusion: isn't it a scoping? Could you clear up? – Alexey Popkov Aug 23 '16 at 19:57
  • 1
    My example was meant as a curiosity rather than as an argument against the premise that a slotted function is effectively not a scoping construct. My example games the system to avoid lexical nesting of slots which, as Mr.Wizard states, cannot be done. I agree with Mr.Wizard's analysis as well as the points made by @LeonidShifrin. – WReach Aug 23 '16 at 20:12
  • @AlexeyPopkov Yes, my statement isn't quite correct, which I actually knew when writing it, but thought it would not be noticed, while I didn't want to further complicate matters. In the example of WReach, the original problem is avoided because entire second function is built at run-time. This isn't scoping in the usual meaning of the word, it rather is a way to substitute scoping with late binding and run-time code generation available in Mathematica. The 'slot parameters visible from anywhere' statement still stands - the late binding does not make slots lexically local. – Leonid Shifrin Aug 23 '16 at 20:12
  • @LeonidShifrin There are two nested Functions in that expression, the inner one builds another Function at run-time. My point is about working of the outer Function: the Slots of the inner one aren't filled from the outer and it looks for me as a kind of scoping. – Alexey Popkov Aug 23 '16 at 20:21
  • @AlexeyPopkov That's not what happens. The inner function (and its slots) does not exist as a single expression at the time when the outer function binds its variables. And the literal Slot passed to the outer function as an argument is just an inert expression without any interpretation. – Leonid Shifrin Aug 23 '16 at 20:23
  • @LeonidShifrin The FullForm is Function[Function[Slot[1][Plus[Slot[2][1], Slot[3]]]][Function, Slot, Slot[1]]]. There indeed already exists the inner Function! It already has some arguments but it exists! – Alexey Popkov Aug 23 '16 at 20:26
  • @AlexeyPopkov The inner function is not the one that is being built from pieces, but the one that builds it. The nesting that is problematic is when parameters of two functions which are at different nesting levels, must appear together (like x+y). In WReach's example, this is not the case: the # in the outer function is bound to the passed argument, while #, #2 and #3 of the inner function are bound to Function, Slot, and # (which by then is already replaced with the passed argument), respectively. Scoping problem is avoided by replacing nesting in space with nesting in time. – Leonid Shifrin Aug 23 '16 at 20:34
  • 1
    @LeonidShifrin My point (may be very silly) is that there are two #: one is bound to the inner Function's first argument (which is verbatim Function) and one is bound to the outer Function's argument (arbitrary), but both are inside of the outer Function. So the # of the inner Function is shielded from the outer. I'm sure I misunderstand something but I feel this as a kind of scoping. – Alexey Popkov Aug 23 '16 at 20:46
  • @LeonidShifrin Actually my point can be reduced to as simple example as # &[a] + # &@b (simple nesting of Functions). It is sufficient to demonstrate that the statement "the slot parameters are visible from anywhere" isn't correct. And this is what make me feeling that we have a kind of scoping here. – Alexey Popkov Aug 23 '16 at 21:24
  • 2
    @AlexeyPopkov Ok, I partly agree. The fact that inner functions protect their slots from being bound in outer functions is indeed a kind of scoping. A 'reduced' one, however - as it does not directly allow for non-trivial nesting. – Leonid Shifrin Aug 23 '16 at 22:39
3

According to Wikipedia,

The strict definition of the (lexical) "scope" of a name (identifier) is unambiguous – it is "the portion of source code in which a binding of a name with an entity applies" – and is virtually unchanged from its 1960 definition in the specification of ALGOL 60.

The behavior of the #-& construct conforms this definition: the binding of the identifier # applies only inside of the Function body excepting the bodies of enclosed other #-& constructs (which use the same identifier and hence mask it):

#@*(#^2 &) &[Sin]
%[x]
Sin@*(#1^2 &)

Sin[x^2]

In this respect nested #-& constructs behave exactly as nested pure functions with identical parameter names:

Function[x, x@*Function[x, x^2]][Sin]
%[x]
Sin@*Function[x, x^2]

Sin[x^2]

So anonymous pure function should be considered as a reduced version of a pure function with named parameters in the sense that the names of the parameters of the former are fixed and can't be changed.

The fact that anonymous pure functions do not rename variables in nested scoping constructs has no relation to the question of whether anonymous pure functions themselves are scoped or not: it is just a matter of implementation of lexical scoping, unique feature of anonymous pure functions (in the Wolfram Language). The above example clearly shows that anonymous pure function is indeed lexically scoped: the parameters of the inner pure function aren't accessible from the outside of its body, and mask the parameters of the enclosing pure function.

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368