5

Is it possible to detect (and create a trigger for) an event when any of the variables defined in the notebook changes its value (for any reason)?

I need to know which variable got changed and what the new value is.

Here are some details:

I have a client-server application, where the client is my Mathematica notebook. I also have a Java layer in between. The server runs MongoDB database and when any client modifies it, the server sends updates to all other clients. Specifically, to the Java layer. The job of the Java layer is to keep an up-to-date local copy of the database, and to relay all updates to the Mathematica client.


Needs["JLink`"]
InstallJava[];
ReinstallJava[ClassPath -> "/Users/verse/Desktop/java-ddp-client.jar"];
LoadJavaClass["java.lang.System"];
LoadJavaClass["java.lang.Object"];
LoadJavaClass["java.lang.reflect.Array"];
LoadJavaClass["com.wolfram.jlink.JLinkClassLoader"];
LoadJavaClass["me.kutrumbos.examples.MessageHandler"];

url = "localhost"; port = 3000; client = JavaNew["me.kutrumbos.DdpClient", url, MakeJavaObject[port]]; observer = JavaNew["me.kutrumbos.examples.ObservableDdpClientObserver"]; collections = {}; onCollectionUpdate[msg_] := Module[{}, collections = observer@getJSONObjectsList["test"];];

observer@addMessageHandler[ ImplementJavaInterface["me.kutrumbos.examples.MessageHandler", "update" -> "onCollectionUpdate"]];

client@addObserver[observer]; (ShowJavaConsole[];) client@connect[];

In this example, variable collections gets updated by the Java layer every time there is a change in the underlying database. It does not happen through the Set command inside the notebook. I need to know when this does happen and I need to properly update other variables.

Conversely, when any of the relevant variables change inside the notebook, I need to be able to update the database on the server using an API request. To send such a request, I need to know which variable has changed and what the new value is.

verse
  • 1,287
  • 6
  • 18
  • See the help for Dynamic there are many examples there. – bill s Feb 07 '14 at 15:59
  • have you seen Monitor[]? – Dr. belisarius Feb 07 '14 at 15:59
  • @bill s, would you mind pointing me to the part that you had in mind? I've read the Dynamic tutorial, but didn't see a way to detect the changes. My understanding was that Dynamic does the updating itself, but does not allow for trigger actions. @belisarius, I think Monitor won't work for my purposes -- it only shows the current value of a variable while a given expression is evaluated, but in my case there is no evaluation. Variables may be changed at any time by an external process (server update). – verse Feb 07 '14 at 16:08
  • 1
    Can you be clearer about what you want to happen? Present a specific scenario (preferably with code) so that we can see what you are trying to do? – bill s Feb 07 '14 at 16:15
  • @user6236 I have marked this question as too broad, because it is not clear what you are trying to do. Because of the way that Dynamic is implemented it may be useful or entirely useless for your task. There are other methods such as ScheduledTask that you should look at too. But outlining all of these methods is IMHO too extensive. – Mr.Wizard Feb 07 '14 at 16:31
  • Ah, I see you were already adding those details. Thank you. – Mr.Wizard Feb 07 '14 at 16:31
  • @user6236 Presumably onCollectionUpdate[msg_] is being called and collections is changing its value. Why can't you just add more code to this function? – Ymareth Feb 07 '14 at 17:10
  • @Ymareth I can, but that's only one of the places where the variables can change -- they can also change within the notebook. – verse Feb 07 '14 at 17:45
  • @user6236 But lets nail down cases. This is the only place the java code can change the state of the notebook? – Ymareth Feb 07 '14 at 17:51
  • @user6236 Also your code above has collections = observer@getJSONObjectsList["test"]; This does use Set. So I'm puzzled why my answer below didn't work. – Ymareth Feb 07 '14 at 17:54
  • @Ymareth I think I was able to isolate all the places where the variables change, and applied your solution. Works so far! Thanks you. – verse Feb 08 '14 at 20:25
  • @user6236 - Welcome. – Ymareth Feb 09 '14 at 16:35

2 Answers2

9

There is ValueFunction, documented here.

It allows detecting value changes for given symbols.

For example,

In[1]:= Experimental`ValueFunction[x] := Print["x changed"]

In[2]:= x = 6

During evaluation of In[10]:= x changed

Out[2]= 6
Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
  • 1
    Why doesn't this have any votes!? +1! – Mr.Wizard May 11 '14 at 17:49
  • From the docs: "ValueFunction takes account of all ways that the value of a symbol can be changed, not just Set." However, even a cursory examination shows that this is totally wrong. So much for "documentation"! – Oleksandr R. May 11 '14 at 20:09
  • @OleksandrR. How did you break it? – Szabolcs May 11 '14 at 21:51
  • Well, it failed with pretty much everything I tried. It seems only to pay attention to OwnValues. Yet, changing even these by any slightly non-standard method (e.g. manipulating OwnValues directly) doesn't trigger it. Nor does changing a part of a value. It would be interesting to know what scenarios it does actually cover. – Oleksandr R. May 11 '14 at 22:01
  • 1
    @Oleksandr I'm not surprised that direct modification of OwnValues doesn't work, at that is expressly outside of "normal" operation. Things like Increment and AppendTo do work, but I am sad to see that as you say assignments to parts do not. Nevertheless I think this still has value. – Mr.Wizard May 12 '14 at 16:20
  • @Oleksandr Also changes by Block and related (Table, Plot, etc.) are caught. Are you aware of any other things that fail besides direct OwnValues manipulation (which again I'm OK with) and Part assignments? – Mr.Wizard May 12 '14 at 16:25
  • @Mr.Wizard well, Increment etc. should of course work because these are implemented in terms of Set anyway. It's encouraging to know that Block and implicit equivalents are handled properly; I didn't get as far as trying this. Loading a dump file containing the monitored symbol also triggers it. So perhaps it isn't as useless as I first thought. I just gave up in disgust after finding that only OwnValues are monitored (though these are far from the only type of values), and even then it doesn't catch everything. If the documentation were more realistic, I wouldn't have objected. – Oleksandr R. May 12 '14 at 17:44
  • @Mr.Wizard The fact that it catches Block-induced temporary value changes might actually be counterproductive in some cases. I have never used this in practice, so I don't know how well it would work for various tasks. If you use it, I'd be curious to see how (just leave a comment here with the link). – Szabolcs May 12 '14 at 19:10
  • Fun..I don't sure you have seen the ?ValueTrack`*.But hard use GeneralUtilities`PrintDefinitions to dig their usage.. – yode Mar 03 '17 at 20:39
4

Something along the lines of ...

a /: Set[a,x_]:=((OwnValues[a]={HoldPattern[a]:>x}; Print[{"a was set to:",x}]); a)

a = 1 prints {a was set to:,1}

a on its own is now 1.

Extends what set does with an additional action which here is Print but ,in principle, could be anything.

Ymareth
  • 4,741
  • 20
  • 28
  • Be aware that this definition will not catch many of the ways that the value of a can change. I shall wait to see what the OP intends before voting on this. – Mr.Wizard Feb 07 '14 at 16:29
  • Unfortunately in my case this method won't work because the variables are updated by the Java layer, and not by the Set command. – verse Feb 07 '14 at 16:33
  • @user6236 I see the question has changed quite a bit since it was first posted. How soon after the collection has changed do you need to trigger your process? (emphasis on "need"). – Ymareth Feb 07 '14 at 16:43
  • @Ymareth I basically need to have consistently synchronized data across all clients and the server. This requires reacting to all the changes as quickly as possible. – verse Feb 07 '14 at 16:48
  • Yes, but I'm trying to get a handle on if you "need" mathematica to respond to every update or you "can live with" the state gathered say every 100ms? Essentially can you reverse the problem and just have mathematica go look every 100ms? – Ymareth Feb 07 '14 at 16:51
  • @Ymareth Yes, I thought of using polling, but my client-side logic is already very CPU-intensive, and using polling very detrimental to the performance. I was very much hoping to find an event-driven way of detecting the changes. – verse Feb 07 '14 at 16:58
  • That would be nice but if its a problem you don't have why add to your woes on a Friday. Mathematica asking "has this changed?" 10 times a second is not going to add to you CPU load in any significant way. – Ymareth Feb 07 '14 at 17:01
  • @Ymareth I did try it -- my laptop can't handle it, even when I parallelize my main application logic. – verse Feb 07 '14 at 17:04
  • 1
    If anybody is willing to investigate or reverse engineer this, take a look at [Internal`Handlers[]](http://mathematica.stackexchange.com/a/1545/12), and see "ValueChange"-related ones are useful. I never dug into this and I don't even know if it works or not. – Szabolcs Feb 07 '14 at 19:36