For cases where one has already assigned DownValues associated with the name \'a\', is there an accepted way to block the assignment of OwnValues to the same name? (I originally
I don't know if this is an "accepted" way, but you could define a rule that prevents Set
and SetDelayed
from acting upon a
:
Remove[a];
a[1] := somethingDelayed
a[2] = somethingImmediate;
a /: HoldPattern[(Set|SetDelayed)[a, _]] := (Message[a::readOnly]; Abort[])
a::readOnly = "The symbol 'a' cannot be assigned a value.";
With this rule in place, any attempt to assign an OwnValue
to a
will fail:
In[17]:= a = somethingThatScrewsUpHeads;
During evaluation of In[17]:= a::readOnly:
The symbol 'a' cannot be assigned a value.
Out[17]= $Aborted
In[18]:= a := somethingThatScrewsUpHeads;
During evaluation of In[18]:= a::readOnly:
The symbol 'a' cannot be assigned a value.
Out[18]= $Aborted
However, this rule will still allow new DownValues
for a
:
In[19]:= a[3] = now;
a[4] := later
In[20]:= a[3]
Out[20]= now
In[21]:= a[4]
Out[21]= later
Performance
The rule does not seem to have an appreciable impact on the performance of Set
and SetDelayed
, presumably since the rule is installed as an up-value on a
. I tried to verify this by executing...
Timing@Do[x = i, {i, 100000000}]
... both before and after the installation of the rule. There was no observable change in the timing. I then tried installing Set
-related up-values on 10,000 generated symbols, thus:
Do[
With[{s=Unique["s"]}
, s /: HoldPattern[(Set|SetDelayed)[s, _]] :=
(Message[s::readOnly]; Abort[])
]
, {10000}]
Again, the timing did not change even with so many up-value rules in place. These results suggest that this technique is acceptable from a performance standpoint, although I would strongly advise performing performance tests within the context of your specific application.
I am not aware of any way to directly "block" OwnValues
, and since Mathematica's evaluator evaluates heads before anything else (parts, application of DownValues
, UpValues
and SubValues
, etc), this does not bring us anywhere (I discussed this problem briefly in my book).
The problem with a straightforward approach is that it will likely be based on adding DownValues
to Set
and SetDelayed
, since it looks like they can not be overloaded via UpValues.
EDIT
As pointed by @WReach in the comments, for the case at hand UpValues
can be successfully used, since we are dealing with Symbol
s which must be literally present in Set
/SetDelayed
, and therefore the tag depth 1
is sufficient. My comment is more relevant to redefining Set
on some heads, and when expressions with those heads must be allowed to be stored in a variable (cases like Part
assignments or custom data types distinguished by heads)
END EDIT
However, adding DownValues
for Set
and SetDelayed
is a recipe for disaster in most cases ( this thread is very illustrative), and should be used very rarely (if at all) and with extreme care.
From the less extreme approaches, perhaps the simplest and safest, but not automatic way is to Protect
the symbols after you define them. This method has a problem that you won't be able to add new or modify existing definitions, without Unprotect
-ing the symbol.
Alternatively, and to automate things, you can use a number of tricks. One is to define custom assignment operators, such as
ClearAll[def];
SetAttributes[def, HoldAll];
def[(op : (Set | SetDelayed))[lhs_, rhs_]] /;
Head[Unevaluated[lhs]] =!= Symbol || DownValues[lhs] === {} :=
op[lhs, rhs]
and consistently wrap SetDelayed
- and Set
-based assignments in def
(I chose this syntax for def
- kept Set
/ SetDelayed
inside def
- to keep the syntax highlighting), and the same for Set
. Here is how your example would look like:
In[26]:=
Clear[a];
def[a[1]:=somethingDelayed];
def[a[2]=somethingImmediate];
def[a=somethingThatScrewsUpHeads];
In[30]:= {a[1],a[2]}
Out[30]= {somethingDelayed,somethingImmediate}
You can then go further and write a code - processing macro, that will wrap SetDelayed
- and Set
-based assignments in def
everywhere in your code:
SetAttributes[useDef, HoldAll];
useDef[code_] := ReleaseHold[Hold[code] /. {x : (_Set | _SetDelayed) :> def[x]}]
So, you can just wrap your piece of code in useDef
, and then don't have to change that piece of code at all. For example:
In[31]:=
useDef[
Clear[a];
a[1]:=somethingDelayed;
a[2]=somethingImmediate;
a=somethingThatScrewsUpHeads;
]
In[32]:= {a[1],a[2]}
Out[32]= {somethingDelayed,somethingImmediate}
In the interactive session, you can go one step further still and set $Pre = useDef
, then you won't forget to wrap your code in useDef
.
EDIT
It is trivial to add diagnostic capabilities to def
, by using the pattern - matcher. Here is a version that will issue a warning message in case when an assignment to a symbol with DownValues
is attempted:
ClearAll[def];
SetAttributes[def, HoldAll];
def::ownval =
"An assignment to a symbol `1` with existing DownValues has been attempted";
def[(op : (Set | SetDelayed))[lhs_, rhs_]] /;
Head[Unevaluated[lhs]] =!= Symbol || DownValues[lhs] === {} := op[lhs, rhs]
def[(Set | SetDelayed)[sym_, _]] :=
Message[def::ownval, Style[HoldForm[sym], Red]];
Again, by using useDef[]
(possibly with $Pre
), this can be an effective debugging tool, since no changes in the original code are at all needed.