5
$\begingroup$

I was working on an answer to this question, when I was sidetracked by what I bring up here.

I wanted a function, which when given a string representing a binary number, would convert the string to a list of digits. I wanted to reject any invalid strings and to return the function expression unevaluated an invalid string was rejected.

I had a lot of trouble debugging the function. My edits to the function body didn't seem have any effect. Finally, in desperation, I got to performing Clear[bitsToDigits] before making an edit. This allowed to complete my debugging, but left me very puzzled because I have come to expect edits which don't effect the first argument of a SetDelayed not to require clearing the function's identifier. In such cases, I have come to expect evaluating SetDelayed to replace the old down-value with the new one.

I decided to look at the down-values after making a second definition of now working function, but not to clear before evaluating it.

bitsToDigits[bitstr_String] :=
  With[{result =
    Module[{valid, bits = Characters[bitstr]},
      valid = StringFreeQ[bitstr, Except["0" | "1"]];
      If[valid, bits, False, False]]},
    result /; result =!= False]

bitsToDigits[bitstr_String] :=
  With[{result = 
          If[StringFreeQ[bitstr, Except["0" | "1"]], 
             Module[{bits = Characters[bitstr]}, bits], False, False]},
    result /; result =!= False]

I now looked at the down-values

DownValues[bitsToDigits]

To my horror, the result was

{
   HoldPattern[bitsToDigits[bitstr_String]] :> 
     With[{result = 
       Module[{valid, bits = Characters[bitstr]}, 
         valid = StringFreeQ[bitstr, Except["0" | "1"]]; 
         If[valid, bits, False, False]]}, 
       result /; result =!= False], 
   HoldPattern[bitsToDigits[bitstr_String]] :> 
     With[{result = 
             If[StringFreeQ[bitstr, Except["0" | "1"]], 
               Module[{bits = Characters[bitstr]}, bits], False, False]}, 
       result /; result =!= False]
 }

The new version of my function will never be executed because the older definition will always match first.

This was totally unexpected. Should I have expected this? Is there something wrong with my definitions of bitsToDigits? If so, how should I fix them? Or have I found a bug?

I have verified that my problem occurs in both V9 and V10 running on OS X.

update

Mr.Wizard has answered my question as originally posted, but I would be keen to learn if there is a way to implement the behavior I want without the distressing side effect, which interferes with my ingrained code development habits.

$\endgroup$
4
  • $\begingroup$ The new version of my function will never be executed because the older definition will always match first it should really have kept only the last one? !Mathematica graphics yes, this is very strange. $\endgroup$
    – Nasser
    Commented Jul 14, 2014 at 5:30
  • $\begingroup$ @Nasser. I suspect it must involve my using Condition. $\endgroup$
    – m_goldberg
    Commented Jul 14, 2014 at 6:27
  • $\begingroup$ oh, I did not even see the Condition you had there, since it was at the bottom of the body, I never saw condition put there! $\endgroup$
    – Nasser
    Commented Jul 14, 2014 at 6:54
  • $\begingroup$ @Nasser Condition has very special behavior when used that way. Please see the middle of this answer for an explanation in my own words: (1852) $\endgroup$
    – Mr.Wizard
    Commented Jul 14, 2014 at 8:43

1 Answer 1

6
$\begingroup$

A Condition is treated as part of the unique pattern of every assignment, even on the right-hand-side:

f := 1 /; foo
f := 2 /; bar

Definition[f]
f := 1 /; foo

f := 2 /; bar

You are using the notably unusual form:

lhs := Module[{vars}, rhs /; test] allows local variables to be shared between test and rhs. You can use the same construction with Block and With. »

Since the body of the evaluation is part of the condition in this syntax it is logical that Mathematica treats this Condition RHS as a unique black box. To do otherwise would greatly limit this mechanism.

For clarity your example can be stripped down to this:

ff[_] := With[{result = foo}, 1 /; result]
ff[_] := With[{result = bar}, 2 /; result]

Definition[ff]
ff[_] := With[{result = foo}, 1 /; result]

ff[_] := With[{result = bar}, 2 /; result]

That is two fully functional definitions for ff existing in parallel, the return value (or non-evaluation) determined by the values of global foo and bar. Despite the fact that the apparent right-hand-side of each /; is result they are clearly different and behave accordingly.


Addressing your updated question and the request for a work-around, the first thing that comes to mind it to simply Unset your left-hand-side before creating a new definition.

This could be automated as follows:

Unprotect[SetDelayed];

SetDelayed[LHS_, RHS_] :=
  Block[{mod`autoUnset = True},
    Quiet @ Unset @ LHS;
    LHS := RHS
  ] /; ! TrueQ[mod`autoUnset]

Protect[SetDelayed];

Now:

ff[_] := With[{result = foo}, 1 /; result]
ff[_] := With[{result = bar}, 2 /; result]

Definition[ff]
ff[_] := With[{result = bar}, 2 /; result]
$\endgroup$
7
  • $\begingroup$ I am satisfied that you have answered my question as originally posed. I have added an update requesting a work-around eliminating Condition. Can you update your answer to supply such a work-around? $\endgroup$
    – m_goldberg
    Commented Jul 14, 2014 at 8:25
  • $\begingroup$ @m_goldberg I'll have to think about that. As I hoped to explain in my answer I think this is reasonable behavior but I also appreciate the desire to customize things to your own liking. I ask however that you more clearly state your parameters. Do you want to look only at the left-hand-side when replacing definitions? Will this operation be done programmatically or does it need to only affect manual definition entry? $\endgroup$
    – Mr.Wizard
    Commented Jul 14, 2014 at 8:32
  • $\begingroup$ @m_goldberg Please take a look at my update and tell me what you think. $\endgroup$
    – Mr.Wizard
    Commented Jul 14, 2014 at 8:42
  • $\begingroup$ Your update looks interesting but beyond my understanding at first glance. I need to study it. What is mod`autoUnset by the way? I've never seen that before. Also, how safe is it (the whole redef of Set) for less than expert users such as myself. $\endgroup$
    – m_goldberg
    Commented Jul 14, 2014 at 8:48
  • $\begingroup$ @m_goldberg Start here: (39711). mod`autoUnset is an arbitrary non-localized Symbol; I figured sticking it in mod` is better than cluttering Global` . This is not safe because it could affect all sorts of internal things that rely on exactly the behavior that you don't want. Consider it a proof of concept. If it superficially works as you wish I will instead create an "environment" function as Leonid often does, which can be used in $Pre to affect only manual input. (Which is why I asked about that.) $\endgroup$
    – Mr.Wizard
    Commented Jul 14, 2014 at 9:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.