General thoughts
I think that your mechanism is reasonably robust for common use cases, but not fully robust if one wants to take into account all possible ways that the value (or, generally, global properties) of the symbol can be changed in Mathematica.
My current opinion is that making such triggering mechanism fully robust without new system support is very hard or may not be possible at all. Such system support may exist but just not be known to me, however. One place to look at are functions Internal`ValueChangeVeto
and Internal`AddHandler["VetoableValueChange", myVetoFunction]
. I do know how to make them work with DownValues
, but not with OwnValues
, though.
Note that, to make your current mechanism more robust, you will have to add similar definitions not just to SetDelayed
, but also to TagSet
and TagSetDelayed
. The interesting thing about them is that while the definitions they create may end up being exactly the same as if you used Set
or SetDelayed
, they use independent assignment mechanism, so assignment like
$var /: $var = 1
won't trigger your mechanism.
Some analysis
One way to more or less systematically analyze this issue is to look at all built-in functions with Hold*
attributes. For functions in the System`
context, one can use something like
Select[
Names["System`*"],
MemberQ[
ToExpression[#, StandardForm, Attributes],
HoldAll | HoldFirst | HoldRest | HoldAllComplete
] &
]
I have manually selected a number of functions from this list which affect, directly or indirectly, some of the global properties of the symbol in question. These functions can be divided into several groups according to their actions. Let us list cases where your mechanism does and does not work, based on such division:
Direct assignment operators: Set
, SetDelayed
, TagSet
, TagSetDelayed
(and possibly also UpSet
and UpSetDelayed
, depending on whether or not you want to account for changes they can introduce). Here also belong UnSet
and TagUnset
.
These functions you can account for explicitly, using definitions similar to what you posted.
In-place modification operators, such as AddTo
, AppendTo
, Decrement
, DivideBy
, Increment
, PreDecrement
, PreIncrement
, PrependTo
, SubtractFrom
, TimesBy
.
Luckily, these are implemented using some of the more fundamental assignment operators, so they are all triggered via your method.
Operators which clear or remove symbols: Clear
, ClearAll
, Remove
, Context
. Whether to include or exclude these is a matter of choice, but all of them are not triggered by your mechanism, but you could add more rules to cover these ones too, in analogy with Set
. I added Context
here, because you can change the context of your variable via
Context[$var] = "NewContext`"
and this effectively removes the variable from its current context.
Functions providing lower-level access to symbol's properties:Attributes
, DefaultValues
, DownValues
, FormatValues
, Messages
, OwnValues
, ClearAttributes
, SetAttributes
, SubValues
, UpValues
. Again, depending on how general you want to be, you can include only some of them, but at least you will have to include OwnValues
.
Direct assignments involving these functions will not trigger your mechanism, e.g.:
OwnValues[$var] = HoldPattern[$var] :> 10
and covering these cases does not seem possible using the UpValue
-based method.
Functions such as Definition
, FullDefinition
, Language`ExtendedDefinition
, or Language`ExtendedFullDefinition
can be assigned to (as you have enlightened us yourself here), for example as
var1 = 10;
Language`ExtendedFullDefinition[$var] =
Language`ExtendedFullDefinition[var1] /. HoldPattern[var1] :> $var
and such assignments will not be triggered by your method. Again, UpValues
won't probably help here.
Block
: it blocks all definitions attached to a symbol, so if the symbol is Block
-ed, assignments inside Block
will not be triggered.
Part assignments: these will not be triggered either. For example:
$var = Range[10];
$var[[1]] = 100
This one also seems beyond the reach of the UpValues
- based method.
I am fairly sure that I missed some more possibilities of value changes for symbols. Some functions in Developer`
, Experimental`
, Internal`
, Language`
and possibly some other contexts may introduce yet more ways to change the values.
Conclusions
I think that what you've got can be made good enough with a modest amount of additional rules, and will cover many or most common use cases. However, making this fully robust requires dedicated system support. It may be that such API already exists somehere in one of the contexts I mentioned earlier, but is just not known to me. As I mentioned already, Internal`ValueChangeVeto
and Internal`AddHandler["VetoableValueChange",...]
may be the right ones to look at, but I don't fully know how they work.
Block
-s it, then the printing would not work for assignments to it made inside thatBlock
. $\endgroup$Set
andSetDelayed
, but from what I can tell, those are rare. $\endgroup$OwnValues
:OwnValues[$var] = HoldPattern[$var] :> 1
does not trigger anything of course. The good news is that things likeAddTo
,Increment
etc are top-level, so that the action is triggered. $\endgroup$$var = Range[10]
, and then$var[[1]] = 10
goes unnoticed by this triggering mechanism. And in this caseUpValues
can not be used since$var
would be too deep insidePart
. $\endgroup$Part
example is a good one. I was thinking of some experiences I had withSparseArray
where I was trying to track changes with this mechanism, and it wasn't captured. $\endgroup$