Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Re: How to short-circuit match failure?

82 views
Skip to first unread message

Leonid Shifrin

unread,
Nov 29, 2010, 6:06:29 AM11/29/10
to
Hi kj,

Here is one way:

In[105]:=

ClearAll[foo]
foo::toolong = "List is too long";
foo::nolist = "First argument is not a list";
foo::nargs = "foo called with `1` argument(s); 2 expected";
Module[{localfoo},
localfoo[x_List /; Length[x] < 3, y_] := {#, y} & /@ x;
localfoo[x_List, y_] :=
"" /; (Message[foo::toolong]; True) && Throw[$Failed, localfoo];
localfoo[x_, y_] :=
"" /; (Message[foo::nolist]; True) && Throw[$Failed, localfoo];
localfoo[x___] :=
"" /; (Message[foo::nargs, Length[{x}]]; True) &&
Throw[$Failed, localfoo];
foo[args___] :=
Module[{result},
result = Catch[localfoo[args], localfoo];
result /; result =!= $Failed]];


In[110]:= foo[{1,2,3},3]
During evaluation of In[110]:= foo::toolong: List is too long
Out[110]= foo[{1,2,3},3]

In[111]:= foo[1,3]
During evaluation of In[111]:= foo::nolist: First argument is not a list
Out[111]= foo[1,3]

In[112]:= foo[1,2,3]
During evaluation of In[112]:= foo::nargs: foo called with 3 argument(s); 2
expected
Out[112]= foo[1,2,3]

In[113]:= foo[{1, 2}, 3]

Out[113]= {{1, 3}, {2, 3}}


Note that by using the local variable <result>, shared between the body and
the condition,
I am able to ensure the standard semantics of function returning
unevaluated, when no
applicable rules are found. I don't see an easy way of achieving this
without this mechanism.

Hope this helps.


Regards,
Leonid


On Sun, Nov 28, 2010 at 2:52 PM, kj <no.e...@please.post> wrote:

>
>
>
> When defining a function as a sequence of rules (with SetDelayed),
> I often want to have messages emitted if the arguments do not have
> the proper form, and then *stop* trying any remaining rules, but
> I don't know how to do the latter.
>
> Here's a silly example:
>
> ClearAll[foo]
> foo::toolong = "List is too long";
> foo::nolist = "First argument is not a list";
> foo::nargs = "foo called with `1` argument(s); 2 expected";
> foo[x_List /; Length[x] < 3, y_] := {#, y} & /@ x
> foo[x_List, y_] /; Message[foo::toolong] = Null
> foo[x_, y_] /; Message[foo::nolist] = Null
> foo[x___] /; Message[foo::nargs, Length[{x}]] = Null
>
> The function foo takes as its first argument a list with length no
> greater than 2. But see what happens when foo[{1, 2, 3}, 3] is
> evaluated:
>
> In[86]:= foo[{1, 2, 3}, 3]
> During evaluation of In[86]:= foo::toolong: List is too long
> During evaluation of In[86]:= foo::nolist: First argument is not a list
> During evaluation of In[86]:= foo::nargs: foo called with 2 argument(s); 2
> expected
> Out[86]= foo[{1, 2, 3}, 3]
>
> The result in Out[86] is as desired, but spurious messages were
> emitted. It's easy to see why. During the evaluation of foo[{1,
> 2, 3}, 3], *all* the rules associated with foo are tried, even
> though the third and fourth ones should not be.
>
> I can prevent this from happening by letting the second rule's
> match succeed; e.g. by defining it like this:
>
> foo[x_List, y_] := (Message[foo::toolong]; Null)
>
> ...but now evaluating foo[{1, 2, 3}, 3] no longer produces the right
> final result (it produces Null, instead of foo[{1, 2, 3}, 3]).
>
> How can I define the second rule for foo so that the third and
> fourth ones are not tried, while the final value for foo[{1, 2,
> 3}, 3] remains as foo[{1, 2, 3}, 3]?
>
> TIA!
>
> ~kj
>
>
>

Leonid Shifrin

unread,
Dec 2, 2010, 5:42:11 AM12/2/10
to
Hi kj,

A nice solution! It offers a good way of separating error-checking code from the
actual one (so that in principle, error - checking code could be generated
programmatically - one just has to parse the declarations (patterns) of the
actual
code).

One small remark: you don't actually have to use the argsok variable,
due to the way conditional definitions work (core[args] is only evaluated if
chkargs[args], which is evaluated first, gives true). So, this will work
just as well:

ClearAll[foo]
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]]


Regards,
Leonid

On Wed, Dec 1, 2010 at 10:14 AM, kj <no.e...@please.post> wrote:

>
> Thanks for all your comments and suggestions. Inspired by them,
> I came up with this general approach for defining a function that
> emits diagnostic messages when called with incorrect arguments,
> and returns the original expression (unevaluated).
>
> The idea is to replace something like this:
>
> ClearAll[foo]
> 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[x_List /; Length[x] < 3, y_] := {#, y} & /@ x

> foo[_List, _] /; Message[foo::toolong] = Null
> foo[_, _] /; Message[foo::nolist] = Null


> foo[x___] /; Message[foo::nargs, Length[{x}]] = Null
>

> with something like this:
>
> ClearAll[foo]
> 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, argsok, core, othervars},
>
> 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;
>
> argsok = chkargs[args];
> argsok && core[args] /; argsok
> ]
>
> The function is defined as a *single* (delayed) rule that evaluates
> to a module with a standard structure. The key points are these:
>
> 1. the rule has a single catchall pattern (args___), and therefore
> it will apply to all invocations of the function;
>
> 2. all the argument-checking is transferred basically verbatim to
> a module-local rule called checkargs; this rule evaluates to
> True whenever the arguments are valid, or to a call to Message
> with the appropriate diagnostic; this terminates the argument-checking,
> so any remaining chkargs rules are skipped;
>
> 3. the function's main action (which assumes all the arguments are
> ok) is defined as one or more module-local delayed rules called
> core; since all the argument matching and checking is delegated
> to the chkargs rule(s), the pattern(s) for the core rule(s) can
> be more general (i.e. simpler) than would be required otherwise
> (e.g. in the example above, the pattern is simply x_, y_);
>
> 4. the last two lines of the module are standard (always the same);
> in the next-to-last line, argument checking is done by evaluating
> chkargs[args} and assigning the reuslt to the module-local
> variable argsok; in the process, diagnostic messages are emitted
> if the checking fails; in this case, argsok will have value
> Null; otherwise, it will have value True;
>
> 5. in the last line, if argsok is True, core[args] is evaluated,
> and returned as the module's evaluation value; if argsok is not
> True (i.e. it is Null), core[args] is not evaluated, and
> furthermore the main module's evaluation will fail thanks to
> the failed /; argsok condition at the end, so the final evaluation
> will yield the original expression, unevaluated;
>
> 6. one feature not illustrated by the simple example above is that
> the DownValues of the chkargs rules can be more elaborate, and
> even set the process of variables local to the main module
> (represented by othervars in the example); these variables can
> be shared with other chkargs rules, or core rules, and thus
> avoid having to perform some calculations needed by several of
> them more than once.
>
> This approach seems pretty general to me, and it is basically a
> wrapper around the "standard" approach.
>
> As always, your comments and criticisms are always welcome.
>
> ~kj
>
>

kj

unread,
Dec 5, 2010, 9:50:39 PM12/5/10
to


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

kj

unread,
Dec 5, 2010, 9:52:48 PM12/5/10
to

My previous post proposed this definition:

> In[16]:=
> ...


>
> 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])
> ]

Sorry, that's grossly inefficient. There's no need to reset
chkargs and core every time foo is evaluated. This is more
reasonable:

In[16]:=
...

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;

foo[args___ /; chkargs[args]] := core[args];
]

~kj

0 new messages