MathGroup Archive 2010

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

Search the Archive

Re: Scoping with Module

  • To: mathgroup at smc.vnet.net
  • Subject: [mg109691] Re: Scoping with Module
  • From: Leonid Shifrin <lshifr at gmail.com>
  • Date: Wed, 12 May 2010 07:33:08 -0400 (EDT)

Hi Patrick,


>From the point of a user I find it very hard to explain to a newbie (who
> started to see the usefulness of lokalized variables) why this works
>
> sol = First@
>   NDSolve[{y'[x] == y[x] Cos[x + y[x]], y[0] == 1}, y[x], {x, 0, 30}];
> func[x_] = y[x] /. sol;
> Plot[func[x], {x, 0, 30}]
>
> while this doen't
>
> Module[{sol, y, x, func},
>  sol = First@
>   NDSolve[{y'[x] == y[x] Cos[x + y[x]], y[0] == 1}, y[x], {x, 0, 30}];
>  func[x_] = y[x] /. sol;
>  Plot[func[x], {x, 0, 30}]
> ]
>


Yes, I agree that this behavior is not intuitive. But I'd say that what the
novice user should be told up front is that she should *not* expect the
first version of your example or similar constructs to work, in the first
place. In principle, in func[x_] = y[x] /. sol, the system is free to rename
x in all lexical occurrences, it just does not do it. So, I think that in a
sense we should blame our own expectations in such cases - we are just too
used to "good".

I think that the documentation should make  much more clear the distinction
between scoping stage and evaluation stage. The fact that your first example
works is a consequence of both the scoping stage behavior (which happens
first), and then the evaluation. In a way, it works only due to the "good
will" of the scoping mechanism which in this case did not rename things. We
should not count on it. So, IMO what must be explained is not why the second
example did not work (this I think is normal), but why the first did.


>
> Additionally, nothing prevents you from doing something like this
>
> t$ = 1;
> Module[{f, g, t},
>  g[t_] := Sin[t];
>  f[t_] = g[t];
>  DownValues[f]
>  ]
>
> Out[2]= {HoldPattern[f$560[t$_]] :> Sin[1]}
>
> Since most system variables are named like $... there is maybe someone who
> accidently uses t$ as additional variable and gets some really strange
> behavior.
>

Yes, I think this is another important point. Lexical scoping in Mathematica
is emulated by symbol renaming, and since it is an emulation, it can be
broken. I think there were many discussions of this issue here on the group,
and the bottom line of all these discussions seems to have been to avoid
using symbols with a dollar sign in their names.


>
> The whole point is really not that important but I just thought that there
> is more behind the idea of the scoping inside Module. Why doesn't has Block
> this behavoir?
>

I think you mean cases like this:

In[1]:= Module[{a}, Hold[Function[b, Print[a + b]][c]]]

Out[1]= Hold[Function[b$, Print[a$592 + b$]][c]]


In[2]:= Block[{a}, Hold[Function[b, Print[a + b]][c]]]

Out[2]= Hold[Function[b, Print[a + b]][c]]


My guess is that the implementation of lexical scoping in Mathematica is
well-aware of the difference between dynamic and lexical scoping. Block is
not dangerous to inner lexical scoping constructs really: its effect is
dynamical, that is - it shows up only at evaluation stage which is already
after all lexical bindings (together with name collision resolution etc)
take place. To put it differently, the action of Block is equivalent to a
(temporal) change in the global state and does not affect lexical scoping
per se, only evaluation.

Therefore, for constructs that do not ordinarily produce new symbols (With,
Function,Set,SetDelayed), it is guaranteed to  be happening already after
they bind their variables. Rule and RuleDelayed seem to protect their
variables against any (dynamic or lexical) outer scoping construct except
themselves, without renaming. For Module, OTOH, Block is harmless simply
because Module produces unique symbols to which external Block-s can not
normally have access (since Block binds dynamically based on the exact name
occurrences), unless again we take some deliberate steps to break the
scoping by for example feeding special symbols to Block.

Outer lexical scoping constructs are however potentially dangerous to inner
ones, since they bind their variables to the body from outside to inside.
So, my guess would be that, when we start to execute code with nested
lexical scoping constructs,  the most external scoping construct first
"looks" at the (held) code(its body) and does the necessary renamings in
inner scoping constructs, then binds its own local variables (after
renamings in inner scoping construsts have happened), and finally executes
its body. At least, this is what I think should be happening (I may be wrong
of course).  OTOH, we also know that Rule and RuleDelayed are different from
other lexical scoping constructs in that they do not respect the inner ones
(unlike say With, Module, Function), which IMO has both a good side (ability
to do scope surgery with them), and a bad side (lots of opportunities for
nasty and subtle scoping bugs).

Anyway, to conclude, one thing I am sure of is this: given that the details
of  internal implementation of scoping constructs (renaming scheme etc) seem
to be manifesting themselves pretty often in examples like the one you gave,
the documentation could at least be more clear and perhaps give some more
details on what is going on behind the scenes, so that at least some of
these things could be understood logically rather than guessed or memorized
like magic spells (for example, I have no idea what percent of my guesses
above about the scoping mechanism is correct).

Regards,
Leonid


> Cheers
> Patrick
>
> Am May 6, 2010 um 12:37 PM schrieb Leonid Shifrin:
>
>
>  Hi Patrick,
>>
>> My understanding is that Mathematica uses aggressive variable renaming
>> (more than is strictly necessary for many cases) in case it sees nested
>> scoping constructs with conflicting variables (variables with the same
>> name).  This manifests itself in the renaming happening if these variables
>> occur not just in the declaration lists, but also in the body, like here:
>>
>> scoping1[{a},scoping2[{b},a+b]
>>
>> In such cases, <b> will be often renamed to something like b$ even before
>> the actual binding happens,
>> for example:
>>
>> In[1]:= Function[a, Function[b, Print[a + b]]][c]
>>
>> Out[1]= Function[b$, Print[c + b$]]
>>
>> Note that in many cases this is not strictly necessary, but it is probably
>> easier and / or more efficient to do this excessive renaming than to work on
>> the case-by-case basis. Now, both Set with patterns and Module are scoping
>> constructs. In your starting examples there are no local variables in the
>> Module declaration , so no conflicts of the above type are possible.
>> However, in this one:
>>
>> Module[{r},
>>  r = {t :> 1};
>>  f[t_] = t /. r;
>>  DownValues[f]
>>  ]
>>
>> you do have a possibility of a conflict of the above type, and so
>> Mathematica renames a dummy variable in the inner scoping construct - <t> in
>>   f[t_] = t /. r; in this case. At the same time, <t> in the
>> r = {t :> 1}; remains global since no patterns / scoping is involved here
>> - thus the result you observed. In your final example:
>>
>> Module[{r},
>>  r = {t :> 1};
>>  r2 = r;
>>  f[t_] = t /. r2;
>>  DownValues[f]
>>  ]
>>
>> while the situation looks pretty similar from the evaluation viewpoint,
>> <r> does not appear inside the definition f[t_] = t /. r2; lexically (this
>> happens only dynamically), and for lexical scoping constructs (at least as
>> they exist in Mathematica) this makes all the difference. At the time of
>> variable name conflict resolution (this is before any code inside Module
>> starts to execute), Mathematica can no longer sense any danger, and thus no
>> renaming happens.
>>
>> I don't know whether this behavior should be sonsidered satisfactory or
>> not, since it is certainly not very intuitive. I'd  say that it's still ok:
>> when we take a line like f[t_] = t /. r;, we should treat <t> as a local
>> lexically scoped variable (the fact that it evaluates to global <t> is
>> related to evaluation, not scoping). Therefore, there is generally no
>> guarantee that injecting there r = {t :> 1}; (which is defined outside the
>> scope of Set in f[t_] = t /. r;) will work. I think that our puzzlement
>> originates from mixing scoping and evaluation. From the fact that Set
>> evaluates the r.h.s and uses global values of <t> for entries of <t> on the
>> r.h.s. (this is what we are used to from our experiences of working with
>> global variables), we subconsciously expect that  this is true for all
>> entries of <t> on the r.h.s, while this is only guaranteed for non-lexical
>> entries of <t> - those that were not explicitly (lexically), but rather
>> dynamically, present in the code before evaluation started. The problem is
>> made worse by the fact that in many common cases  it does work  (like the
>> case of global variables and no surrounding scoping constructs).
>>
>> The bottom line is that IMO the construct you looked at is not reliable
>> from the start in that it attempts to do scope engineering and the results
>> are then necessarily dependent on the details of Mathematica internal
>> implementation of lexical scoping constructs. Then it is probably a matter
>> of taste whether or not to use constructs like this. Personally, I do it
>> from time to time for my own purposes, but I would hesitate to put them into
>> "production" code. Besides, I believe that in most cases there  exist more
>> reliable alternatives.
>>
>> Regards,
>> Leonid
>>
>>
>>
>> On Thu, May 6, 2010 at 12:52 PM, Patrick Scheibe <
>> pscheibe at trm.uni-leipzig.de> wrote:
>> Hi,
>>
>> I cannot explain the following behaviour of Module. Maybe someone else
>> can:
>>
>> in the global scope
>>
>> f[t_] = t;
>> DownValues[f]
>>
>> results in {HoldPattern[f[t_]] :> t} as expected. Simply wrapping it
>> with Module, without
>> making f lokal does not change this behaviour.
>>
>> Module[{},
>>  f[t_] = t;
>>  DownValues[f]
>>  ]
>>
>> results still in {HoldPattern[f[t_]] :> t}.
>> Say I want to replace the right side of the definition with a rule
>>
>> r = {t :> 1};
>> f[t_] = t /. r;
>> DownValues[f]
>>
>> and we get {HoldPattern[f[t_]] :> 1}
>>
>> Module[{},
>>  r = {t :> 1};
>>  f[t_] = t /. r;
>>  DownValues[f]
>>  ]
>>
>> still the expected result {HoldPattern[f[t_]] :> 1}
>>
>> Now (sorry for the lengthy introduction) making r lokal
>>
>> Module[{r},
>>  r = {t :> 1};
>>  f[t_] = t /. r;
>>  DownValues[f]
>>  ]
>>
>> and we get {HoldPattern[f[t$_]] :> t$}. The Pattern t_ was renamed
>> before the replacement could act.
>> Using the local variable r to set a global r2 and it's working again.
>>
>> Module[{r},
>>  r = {t :> 1};
>>  r2 = r;
>>  f[t_] = t /. r2;
>>  DownValues[f]
>>  ]
>>
>> So there must be at least one thing about Module I did'nt understand.
>> I thought the main difference between Block
>> and Module is the lexical scoping!? But the only thing which was
>> renamed is the lokal usage of r.
>> Any ideas?
>>
>> Cheers
>> Patrick
>>
>> 7.0 for Mac OS X x86 (64-bit) (February 19, 2009)
>>
>>
>>
>


  • Prev by Date: Re: Part specification... is neither an integer nor a list of integers
  • Next by Date: Re: Trying to remove item from list of filenames
  • Previous by thread: Re: Scoping with Module
  • Next by thread: Easy question, please help to run a function n times