MathGroup Archive 2010

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

Search the Archive

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


  • Prev by Date: Re: [Please Help] How to get coefficient list from a
  • Next by Date: Re: [Please Help] How to get coefficient list from a
  • Previous by thread: Re: How to short-circuit match failure?
  • Next by thread: Re: Finding local maxima in multidimensional array (efficiently)