Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

Some built-in variables trigger actions when their values are changed:

In[17]:= $Assumptions=1
    During evaluation of In[17]:= $Assumptions::bass: 1 is not a well-formed assumption. >>
Out[17]= 1

In[18]:= $Assumptions:=2
    During evaluation of In[18]:= $Assumptions::bass: 2 is not a well-formed assumption. >>

(Interestingly the message doesn't trigger if it's set again to the same value.)

How can we safely implement something like this? Are the any built-in hooks for value changes?


This is what I currently have. Does it look safe? Does anyone foresee problems? (There's always the least expected thing that goes wrong with these...)

$var /: Set[$var, value_] := 
   Block[{$in = True}, 
         Print["$var set to ", value];  $var = value] /; Not@TrueQ[$in]

$var = 6

(*
  $var set to 6
  6
 *)

$in would of course be in a private context and the same needs to be done for SetDelayed as well.

share|improve this question
    
Looks safe enough for me. I use the same thing myself when I need this sort of functionality. Of course, I may be unaware of something. Also, if you expose this variable and someone Block-s it, then the printing would not work for assignments to it made inside that Block. –  Leonid Shifrin Apr 24 '13 at 19:24
    
I've used this, too, without issue. To add to what Leonid said, there are a couple of cases where the underlying data can be changed, bypassing Set and SetDelayed, but from what I can tell, those are rare. –  rcollyer Apr 24 '13 at 19:39
    
@rcollyer One of such cases is when one uses direct manipulations with OwnValues:OwnValues[$var] = HoldPattern[$var] :> 1 does not trigger anything of course. The good news is that things like AddTo, Increment etc are top-level, so that the action is triggered. –  Leonid Shifrin Apr 24 '13 at 19:40
1  
@rcollyer Another case, perhaps more important in practice, is for part assignments, which are not triggered: $var = Range[10], and then $var[[1]] = 10 goes unnoticed by this triggering mechanism. And in this case UpValues can not be used since $var would be too deep inside Part. –  Leonid Shifrin Apr 24 '13 at 19:46
    
@LeonidShifrin the Part example is a good one. I was thinking of some experiences I had with SparseArray where I was trying to track changes with this mechanism, and it wasn't captured. –  rcollyer Apr 24 '13 at 19:49

1 Answer 1

up vote 11 down vote accepted

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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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