12
$\begingroup$

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.

$\endgroup$
8
  • $\begingroup$ 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. $\endgroup$ Commented Apr 24, 2013 at 19:24
  • $\begingroup$ 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. $\endgroup$
    – rcollyer
    Commented Apr 24, 2013 at 19:39
  • $\begingroup$ @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. $\endgroup$ Commented Apr 24, 2013 at 19:40
  • 1
    $\begingroup$ @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. $\endgroup$ Commented Apr 24, 2013 at 19:46
  • $\begingroup$ @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. $\endgroup$
    – rcollyer
    Commented Apr 24, 2013 at 19:49

1 Answer 1

12
$\begingroup$

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.

$\endgroup$
5
  • $\begingroup$ You may add something about ValueFunctionmathematica.stackexchange.com/a/87554/5478 $\endgroup$
    – Kuba
    Commented Jul 11, 2016 at 12:28
  • $\begingroup$ @Kuba Don't have the time for it right now. Feel free to edit it in if you think you have something good to add. $\endgroup$ Commented Jul 11, 2016 at 12:39
  • $\begingroup$ Don't have nothing to add really, maybe let's just leave a link to Karsten's example :) $\endgroup$
    – Kuba
    Commented Jul 11, 2016 at 12:44
  • $\begingroup$ @Kuba Just checked: ValueFunction doesn't work when we change e.g. OwnValues of a symbol directly. So, it doesn't work according the spec, which says "ValueFunction takes account of all ways that the value of a symbol can be changed, not just Set". So, as of now, I don't see a point to add it to the above, since its presence doesn't contradict my conclusions in the answer above. $\endgroup$ Commented Jul 11, 2016 at 18:45
  • $\begingroup$ Good find, thanks for checking. $\endgroup$
    – Kuba
    Commented Jul 11, 2016 at 18:58

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.