Re: How to short-circuit match failure?
- To: mathgroup at smc.vnet.net
- Subject: [mg114465] Re: How to short-circuit match failure?
- From: kj <no.email at please.post>
- Date: Sun, 5 Dec 2010 21:50:48 -0500 (EST)
- References: <id7t63$lc6$1@smc.vnet.net>
In my previous post on this thread I wrote the following:
> One workaround is to change Module to Block, since Block does not
> create any temporary variables in the first place. Of course,
> using Block is trickier than using Module, because there's the risk
> that a block variable will localize the value of a *global* variable
> that is used by some other function invoked during the evaluation
> of the Block. (There may be a way to use Unique in a way that
> automatically prevents this problem with Block without requiring
> any particular care from the programmer, but, if so, it is beyond
> my Mathematica skill to devise it.)
Please, ignore the last parenthetical remark! It's insane at best
(stupid at worst). I show a better solution below, one that still
uses Module rather than Block, and thus retains Module's lexical
encapsulation, but avoids the exploding context problem I referred
to in my previous post.
First, let me illustrate this "exploding context" problem. In
preparation for this I define a symbol that will useful to examine the
names in the current context:
In[1]:= System`names := Names[$Context <> "*"]
(I define it in the System` context to keep it out $Context (which
is Global` by default), and still be able to refer to it with its
unqualified name. Note that it's a SetDelayed definition, so
evaluating this symbol will produce a state-dependent result.)
Now I recapitulate the definition of foo I proposed before, (enhanced
with the elegant simplification that Leonid Shifrin proposed in an
earlier post):
In[2]:= foo::toolong = "First argument is too long";
foo::nolist = "First argument is not a list";
foo::nargs = "foo called with `1` argument(s); 2 expected";
foo[args___] := Module[{chkargs, core},
chkargs[x_List /; Length[x] < 3, y_] := True;
chkargs[_List, _] := Message[foo::toolong];
chkargs[_, _] := Message[foo::nolist];
chkargs[x___] := Message[foo::nargs, Length[{x}]];
core[x_, y_] := {#, y} & /@ x;
core[args] /; chkargs[args]
]
Now we observe the "exploding context" phenomenon. First, a single
evaluation of foo:
In[6]:= foo[{1}, 2];
If we examine the context now, we'll find a few lingering "zombies"
of temporary variables, as I described in another thread (here I
get to use the utility "names" symbol I defined in In[1]; the
zombies are the ones whose names contain "$"):
In[7]:= names
Out[7]= {"args", "chkargs", "chkargs$", "core", "core$", "core$600", \
"foo", "x", "x$", "y", "y$"}
In[8]:= Length[names]
Out[8]= 11
Now, we evaluate foo many times, and afterwards count the current
number of names in the context:
In[9]:= Do[foo[{1}, 2], {1000}]
In[10]:= Length[names]
Out[10]= 1011
As you can see, one new name is added to the context per evaluation
of foo. These names are all temporary variables whose names begin
with core$:
In[11]:= Select[names, StringMatchQ[#, "core$" ~~ __] &] // Short
Out[11]//Short= {"core$1000", "core$1001", "core$1002", "<<995>>", \
"core$997", "core$998", "core$999"}
In[12]:= Length[%]
Out[12]= 1001
That's what I mean by an "exploding context." The solution below
avoids this explosion, while still taking advantage of Module's
lexical scoping.
First, I clear the context (turning off messages that Remove emits
when it is applied to a temporary variable; that's another
zombie-related annoyance):
In[13]:= Off[Remove::rmnsm];
Scan[Remove, names]
On[Remove::rmnsm];
The new definition of foo is almost identical to the first definition,
but it puts the Module scope *around* the SetDelayed instead of as its
RHS. Hence, Module expression needs to be evaluated only once. Now,
the SetDelayed's RHS is a single parentheses-delimited compound
expression, needing no scoping of its own because it relies on the
scoping provided by the surrounding Module:
In[16]:=
foo::toolong = "First argument is too long";
foo::nolist = "First argument is not a list";
foo::nargs = "foo called with `1` argument(s); 2 expected";
Module[{chkargs, core},
foo[args___] := (
chkargs[x_List /; Length[x] < 3, y_] := True;
chkargs[_List, _] := Message[foo::toolong];
chkargs[_, _] := Message[foo::nolist];
chkargs[x___] := Message[foo::nargs, Length[{x}]];
core[x_, y_] := {#, y} & /@ x;
core[args] /; chkargs[args])
]
Unfortunately, this new definition does not avoid zombies entirely:
In[20]:= foo[{1}, 2];
In[21]:= names
Out[21]= {"args", "args$", "chkargs", "chkargs$1601", "core", "core$1601",
"foo", "x", "x$", "y", "y$"}
But at least it avoids the "exploding context" problem:
In[22]:= Length[%]
Out[22]= 11
In[23]:= Do[foo[{1}, 2], {1000}]
In[24]:= Length[names]
Out[24]= 11
Importantly, this new definition produces the same output and emits
the same messages as the original one:
In[25]:= foo[{1, 2}, 3]
Out[25]= {{1, 3}, {2, 3}}
In[26]:= foo[{1, 2, 3}, 4]
During evaluation of In[26]:= foo::toolong: First argument is too long
Out[26]= foo[{1, 2, 3}, 4]
Still, the new implementation's remaining zombies can still cause
bizarre, difficult-to-debug core::shdw messages in some situations:
In[27]:= Write["DEMO.m", OutputForm["BeginPackage[\"DEMO`\"]"]]
Write["DEMO.m", OutputForm["Module[{core},1/;True];"]]
Write["DEMO.m", OutputForm["EndPackage[]"]]
Close["DEMO.m"];
In[31]:= Get["DEMO`"]
During evaluation of In[31]:= core::shdw: Symbol core appears in
multiple contexts {DEMO`,Global`}; definitions in context DEMO` may
shadow or be shadowed by other definitions. >>
The more I think about it, the harder it is for me to see these
zombies as anything other than a bug in Mathematica. A "temporary
variable" that lingers indefinitely is in breach of contract. Then
again, I don't know how many versions of Mathematica have had this
potential for such zombies. I imagine that if this behavior has
survived long enough for me to find it, it probably is not causing too
much problems in practice. Also, I am still very green with
Mathematica contexts and packages (meaning that I am still routinely
surprised and befuddled by context-related behaviors), so it is
difficult for me from this inexperienced vantage point to have a clear
idea of how much of a problem these zombies can be in practice.
Thanks for reading this far. This was an instructive exercise for me.
I hope some of you find it instructive too.
~kj