Reasons why adding rules to Set
is a really bad idea
First, let me list the reasons why I think that adding rules to Set
globally is a very bad practice:
- This is a hugely non-local system modification. We have no idea which parts of the system will be affected, but we can be sure that there will be many.
Set
is a very frequently used command (see first point)
Set
is fundamental to the system, and in some ways more low-level command than most others.
- It could have been already overloaded internally for certain purposes. We may even break that internal code, and in this case, there would be absolutely no way to know,
- We may degrade the performance, in unpredictable ways.
Some possible ways out
Now, here are some suggestions of what one could do.
- Overload
Set
via UpValues
, when you can. One example can be found here. It is not always possible however, due to the limitation on the depth-1 UpValues search.
- Define yout own custom assignment operator, like
mySet
, and use that. This is the option I use most frequently myself. A bit more typing and less syntactically pleasing, but saves a lot of hassle in the long term. Besides, custom assignment operators are a very powerful programming tool, because you can do some extra stuff along with making assignments.
- Create local environments. These can be lexical or dynamic. I will illustrate with a dynamic environment, for a simple example of a type
point
, that will hold a list of 2-dimensional coordinates. Our goal is that if some variable var
is of type point
(i.e. holds an expression like point[{x,y}]
, then if I make an assignment like var = {3,4}
, I should have now point[{3,4}]
stored in var
.
Here is the code:
ClearAll[withCustomSet];
SetAttributes[withCustomSet, HoldAll];
withCustomSet[code_] :=
Internal`InheritedBlock[{Set},
Unprotect[Set];
Set[var_Symbol, {x_, y_}] /;
MatchQ[HoldComplete[var] /. OwnValues[var], HoldComplete[_point]] :=
var[[1]] = {x, y};
Protect[Set];
code];
Let us see:
a = b = point[{1,2}]
a = {3,4};
a
(*
==>{3,4}
*)
while
withCustomSet[b = {3,4}];
b
(*
==> point[{3,4}]
*)
In practice, you can execute arbitrary code inside withCustomSet
, and the new redefinition of Set
will take effect all the way down the execution stack. This is powerful but at the same time dangerous, however, much less dangerous that an analogous but global redefinition.
A lexical environment is also easy to construct:
ClearAll[withCustomSetLex];
SetAttributes[withCustomSetLex, HoldAll];
withCustomSetLex[code_] :=
Unevaluated[code] /.
HoldPattern[
Set[var_Symbol, {x_, y_}] /;
MatchQ[HoldComplete[var] /. OwnValues[var], HoldComplete[_point]]] :>
(var[[1]] = {x, y});
You can test that it works fine with the same simple test as above. However, it will only affect the instances of Set
explicitly present in the code inside it. OTOH, this is yet much safer, since the stack is not affected.
Summary
- Don't ever add rules to
Set
globally, if you want predictable behavior from Mathematica
- There are plenty of ways to go around this problem, so this is not that serious of a limitation, really.
EDIT
To address the specific question (added in the update): the lexical environment would look like
ClearAll[withCustomSetLex];
SetAttributes[withCustomSetLex, HoldAll];
withCustomSetLex[code_] :=
Unevaluated[code] /.
HoldPattern[Set[symbol_[key_], value_]] :> ObjectSet[symbol, key, value]
A dynamic environment is trivial to implement: replace the code I used above in between Unprotect[Set]
and Protect[Set]
with your code.
Set
. Otherwise it's of course impossible to provide an alternative. If the problem is [this question](mathematica.stackexchange.com/questions/990), then answers should go in that thread instead, right? Do you see my point? $\endgroup$