How can I implement a listener variable in Mathematica?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
3
down vote

favorite
1












I'd like to be able to listen to a variable and know when it's been changed and accept/reject the change as well as use it as an update listener for Dynamic how can I do this?










share|improve this question























  • Do you need this functionality contained in the Manipulate or is something external to the Manipulate updating the variable? If external then is it in the same Module`DynamicModule` or elsewhere?
    – Edmund
    3 hours ago











  • @Edmund I was imagining this would be a pure kernel thing without reference to Manipulate or Dynamic (although usable by the latter).
    – b3m2a1
    2 hours ago














up vote
3
down vote

favorite
1












I'd like to be able to listen to a variable and know when it's been changed and accept/reject the change as well as use it as an update listener for Dynamic how can I do this?










share|improve this question























  • Do you need this functionality contained in the Manipulate or is something external to the Manipulate updating the variable? If external then is it in the same Module`DynamicModule` or elsewhere?
    – Edmund
    3 hours ago











  • @Edmund I was imagining this would be a pure kernel thing without reference to Manipulate or Dynamic (although usable by the latter).
    – b3m2a1
    2 hours ago












up vote
3
down vote

favorite
1









up vote
3
down vote

favorite
1






1





I'd like to be able to listen to a variable and know when it's been changed and accept/reject the change as well as use it as an update listener for Dynamic how can I do this?










share|improve this question















I'd like to be able to listen to a variable and know when it's been changed and accept/reject the change as well as use it as an update listener for Dynamic how can I do this?







front-end customization symbols






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 8 hours ago

























asked 8 hours ago









b3m2a1

24k151144




24k151144











  • Do you need this functionality contained in the Manipulate or is something external to the Manipulate updating the variable? If external then is it in the same Module`DynamicModule` or elsewhere?
    – Edmund
    3 hours ago











  • @Edmund I was imagining this would be a pure kernel thing without reference to Manipulate or Dynamic (although usable by the latter).
    – b3m2a1
    2 hours ago
















  • Do you need this functionality contained in the Manipulate or is something external to the Manipulate updating the variable? If external then is it in the same Module`DynamicModule` or elsewhere?
    – Edmund
    3 hours ago











  • @Edmund I was imagining this would be a pure kernel thing without reference to Manipulate or Dynamic (although usable by the latter).
    – b3m2a1
    2 hours ago















Do you need this functionality contained in the Manipulate or is something external to the Manipulate updating the variable? If external then is it in the same Module`DynamicModule` or elsewhere?
– Edmund
3 hours ago





Do you need this functionality contained in the Manipulate or is something external to the Manipulate updating the variable? If external then is it in the same Module`DynamicModule` or elsewhere?
– Edmund
3 hours ago













@Edmund I was imagining this would be a pure kernel thing without reference to Manipulate or Dynamic (although usable by the latter).
– b3m2a1
2 hours ago




@Edmund I was imagining this would be a pure kernel thing without reference to Manipulate or Dynamic (although usable by the latter).
– b3m2a1
2 hours ago










2 Answers
2






active

oldest

votes

















up vote
5
down vote













As part of handling this for myself I wrote a package that does it. The idea was to mimic the *Var() classes in tkinter. I'll do a quick demo then explain how I built this, which is the actually interesting part of this.



First we'll load that package and make one of these Listener objects:



Get["https://github.com/b3m2a1/mathematica-tools/raw/master/Listeners.m"]

var=Listener["b"];
var//InputForm
(*Out: Listener["b"] *)


It looks like nothing happened, but we'll look at the proper display form:



displayedForm[expr_] :=
First@FrontEndExecute@
ExportPacket[Cell[BoxData@ToBoxes@expr], "PlainText"]

displayedForm@var
(* Out: "Null" *)


So the object is displaying as Null.



Now we'll assign something new to var:



var = 1;
displayedForm@var
(* Out: "1" *)

OwnValues@var//InputForm
(* Out: HoldPattern[var] :> Listener["b"] *)


And now even though it displays as 1 the value of var hasn't changed at all.



Next we'll attach a callback that simply tells us what kind of change is being applied to this Listener object:



var["Callback"]=Print[#]&;
var=2
(* Print: Update *)
(* Out: 2 *)
var=1
(* Print: Update *)
(* Out: 1 *)
var[[1]] = 5
(* Print: UpdatePart *)
(* Out: 5 *)


This callback is telling us the type of change applied. It's also passed the name ("b" in this case) and the args of the change and if it returns False the change is rejected.



Now we'll get to the original reason this was made, which is for forcing updated in Dynamic. If we use:



ListenerDynamic[RandomReal, var]


every time var is updated the argument (RandomReal) gets re-evaluated.



Updates to var, while slow relative to standard variable assignment, are still faster than box updates, so we can the classic:



ListenerDynamic[var = RandomReal, var]


And watch it tick away. I haven't supported Increment and AddTo and things, so don't get too crazy with it, but it would be easy to add.



Unlike Dynamic[x = RandomReal], though, we can turn this off by setting: var["Callback"] = False &



So, great, we got what we came for. We effectively have a Tk *Var* object.



Implementation



But what's fun about this is how it's done. I didn't really do anything fundamentally different than my OOP work here but I used a completely different backend that could have been handled in a very different way to very different effect.



Language`ExpressionStore



To implement this I used the new Language`ExpressionStore. These are "weak" hash maps in that they store values without messing with the ref-counts of stored object and they use a specific object instance as a key and when that object is destroyed all its associated keys and values in the map are destroyed.



There's an example of this in the linked question, but we can see how it works here. First we'll make a new object and get the variable it uses for storing state:



$HistoryLength = 0 (* to prevent ref count changes from Out *)
obj = Listener["object"];
obj["Variable"]

(* Out: Hold[Listeners`Private`listener$21210] *)


This Symbol is what is doing all the state holding and is what that ListenerDynamic is looking for updates on.



Now we'll do some memory tests. First we'll create a large array any attach it to the object:



mem1 = MemoryInUse;

obj = RandomReal[-1, 1, 100, 100, 100];
mem2 = MemoryInUse;
mem2 - mem1

(* Out: 8000368 *)


And then we'll simply remove any reference obj as a variable has to its held Listener object in a way where I couldn't have even attached UpValues to mess with things:



OwnValues[obj] = ;
mem3 = MemoryInUse;
mem3 - mem1

(* Out: -1128 *)


And we see that all the memory associated with obj has been freed. And if we check the definitions on that storage symbol:



Listeners`Private`listener$21210

(* Out: Listeners`Private`listener$21210 *)


We see it cleaned itself up. This is because it was a Temporary symbol and once there were no references to the Listener object that was its key in the underlying ExpressionStore both of them were cleaned up.



Language`MutationHandler



We use ExpressionStore as our backend and then as the interface (front-end?) to the object we use the Language`*Mutation* functions to allow us to cleanly allow var = x to call a custom setter instead of simply calling Set on var.



System`Private`*Entry*



I also use the System`Private`*Entry* functions as a way to test whether my objects have truly been "constructed" or not. If we get a raw Listener[...] expression we can check whether System`Private`HoldEntryQ is true or not and if it is we call a constructor that will return a Listener[...] object that looks the same but is a fundamentally different object with System`Private`HoldSetNoEntry called on it.



Object lookup



I wanted to be able to just use Listener[name] raw without ensuring I had the variable bound as a convenience, so I actually had to check whether an object with that name had already been built. I did this by searching the keys in the expression store I built (Listeners`Package`$Listeners). This is slightly inefficient, but it only happens in the construction step, so I thought the time hit was worth taking.



New forms of OOP



One nice thing about this form of OOP is that it allows us to have more opaque objects than I used in my SymbolObjects package. We could simply use ExpressionStore as a way to store all fields and methods. The memory will be naturally cleaned when whatever object we return from our constructor goes out of scope.



This is both cleaner and potentially more robust than writing interfaces to Symbol or Association.



I wouldn't be surprised to see a lot more development from WRI and package developers around here in that vein.






share|improve this answer



























    up vote
    1
    down vote













    You may use PersistentValue with its ValuePreprocessingFunction option.



    For some listening function such as



    listen = Echo;


    included in a validation function



    validateInteger[x_] := If[IntegerQ[x], listen@x; x, $Failed]


    then a PersistentValue can be created which is validated and notifies updates via the ValuePreprocessingFunction.



    PersistentValue["int", PersistenceLocation["KernelSession"], 
    ValuePreprocessingFunction -> validateInteger] = 0;



    » 0




    When set to a value that validates the listen function is called and the PersistentValue updated.



    PersistentValue["int", "KernelSession"] = 6



    » 6



    6



    When set to a value that does not validate the listen function is not called and the PersistentValue is not updated.



    PersistentValue["int", "KernelSession"] = 6.5



    $Failed



    PersistentValue["int", "KernelSession"]



    6



    Clean up with



    DeleteObject[PersistentObjects["int"]];


    Hope this helps.






    share|improve this answer




















      Your Answer




      StackExchange.ifUsing("editor", function ()
      return StackExchange.using("mathjaxEditing", function ()
      StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
      StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["$", "$"], ["\\(","\\)"]]);
      );
      );
      , "mathjax-editing");

      StackExchange.ready(function()
      var channelOptions =
      tags: "".split(" "),
      id: "387"
      ;
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function()
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled)
      StackExchange.using("snippets", function()
      createEditor();
      );

      else
      createEditor();

      );

      function createEditor()
      StackExchange.prepareEditor(
      heartbeatType: 'answer',
      convertImagesToLinks: false,
      noModals: false,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      );



      );













       

      draft saved


      draft discarded


















      StackExchange.ready(
      function ()
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fmathematica.stackexchange.com%2fquestions%2f182617%2fhow-can-i-implement-a-listener-variable-in-mathematica%23new-answer', 'question_page');

      );

      Post as a guest






























      2 Answers
      2






      active

      oldest

      votes








      2 Answers
      2






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes








      up vote
      5
      down vote













      As part of handling this for myself I wrote a package that does it. The idea was to mimic the *Var() classes in tkinter. I'll do a quick demo then explain how I built this, which is the actually interesting part of this.



      First we'll load that package and make one of these Listener objects:



      Get["https://github.com/b3m2a1/mathematica-tools/raw/master/Listeners.m"]

      var=Listener["b"];
      var//InputForm
      (*Out: Listener["b"] *)


      It looks like nothing happened, but we'll look at the proper display form:



      displayedForm[expr_] :=
      First@FrontEndExecute@
      ExportPacket[Cell[BoxData@ToBoxes@expr], "PlainText"]

      displayedForm@var
      (* Out: "Null" *)


      So the object is displaying as Null.



      Now we'll assign something new to var:



      var = 1;
      displayedForm@var
      (* Out: "1" *)

      OwnValues@var//InputForm
      (* Out: HoldPattern[var] :> Listener["b"] *)


      And now even though it displays as 1 the value of var hasn't changed at all.



      Next we'll attach a callback that simply tells us what kind of change is being applied to this Listener object:



      var["Callback"]=Print[#]&;
      var=2
      (* Print: Update *)
      (* Out: 2 *)
      var=1
      (* Print: Update *)
      (* Out: 1 *)
      var[[1]] = 5
      (* Print: UpdatePart *)
      (* Out: 5 *)


      This callback is telling us the type of change applied. It's also passed the name ("b" in this case) and the args of the change and if it returns False the change is rejected.



      Now we'll get to the original reason this was made, which is for forcing updated in Dynamic. If we use:



      ListenerDynamic[RandomReal, var]


      every time var is updated the argument (RandomReal) gets re-evaluated.



      Updates to var, while slow relative to standard variable assignment, are still faster than box updates, so we can the classic:



      ListenerDynamic[var = RandomReal, var]


      And watch it tick away. I haven't supported Increment and AddTo and things, so don't get too crazy with it, but it would be easy to add.



      Unlike Dynamic[x = RandomReal], though, we can turn this off by setting: var["Callback"] = False &



      So, great, we got what we came for. We effectively have a Tk *Var* object.



      Implementation



      But what's fun about this is how it's done. I didn't really do anything fundamentally different than my OOP work here but I used a completely different backend that could have been handled in a very different way to very different effect.



      Language`ExpressionStore



      To implement this I used the new Language`ExpressionStore. These are "weak" hash maps in that they store values without messing with the ref-counts of stored object and they use a specific object instance as a key and when that object is destroyed all its associated keys and values in the map are destroyed.



      There's an example of this in the linked question, but we can see how it works here. First we'll make a new object and get the variable it uses for storing state:



      $HistoryLength = 0 (* to prevent ref count changes from Out *)
      obj = Listener["object"];
      obj["Variable"]

      (* Out: Hold[Listeners`Private`listener$21210] *)


      This Symbol is what is doing all the state holding and is what that ListenerDynamic is looking for updates on.



      Now we'll do some memory tests. First we'll create a large array any attach it to the object:



      mem1 = MemoryInUse;

      obj = RandomReal[-1, 1, 100, 100, 100];
      mem2 = MemoryInUse;
      mem2 - mem1

      (* Out: 8000368 *)


      And then we'll simply remove any reference obj as a variable has to its held Listener object in a way where I couldn't have even attached UpValues to mess with things:



      OwnValues[obj] = ;
      mem3 = MemoryInUse;
      mem3 - mem1

      (* Out: -1128 *)


      And we see that all the memory associated with obj has been freed. And if we check the definitions on that storage symbol:



      Listeners`Private`listener$21210

      (* Out: Listeners`Private`listener$21210 *)


      We see it cleaned itself up. This is because it was a Temporary symbol and once there were no references to the Listener object that was its key in the underlying ExpressionStore both of them were cleaned up.



      Language`MutationHandler



      We use ExpressionStore as our backend and then as the interface (front-end?) to the object we use the Language`*Mutation* functions to allow us to cleanly allow var = x to call a custom setter instead of simply calling Set on var.



      System`Private`*Entry*



      I also use the System`Private`*Entry* functions as a way to test whether my objects have truly been "constructed" or not. If we get a raw Listener[...] expression we can check whether System`Private`HoldEntryQ is true or not and if it is we call a constructor that will return a Listener[...] object that looks the same but is a fundamentally different object with System`Private`HoldSetNoEntry called on it.



      Object lookup



      I wanted to be able to just use Listener[name] raw without ensuring I had the variable bound as a convenience, so I actually had to check whether an object with that name had already been built. I did this by searching the keys in the expression store I built (Listeners`Package`$Listeners). This is slightly inefficient, but it only happens in the construction step, so I thought the time hit was worth taking.



      New forms of OOP



      One nice thing about this form of OOP is that it allows us to have more opaque objects than I used in my SymbolObjects package. We could simply use ExpressionStore as a way to store all fields and methods. The memory will be naturally cleaned when whatever object we return from our constructor goes out of scope.



      This is both cleaner and potentially more robust than writing interfaces to Symbol or Association.



      I wouldn't be surprised to see a lot more development from WRI and package developers around here in that vein.






      share|improve this answer
























        up vote
        5
        down vote













        As part of handling this for myself I wrote a package that does it. The idea was to mimic the *Var() classes in tkinter. I'll do a quick demo then explain how I built this, which is the actually interesting part of this.



        First we'll load that package and make one of these Listener objects:



        Get["https://github.com/b3m2a1/mathematica-tools/raw/master/Listeners.m"]

        var=Listener["b"];
        var//InputForm
        (*Out: Listener["b"] *)


        It looks like nothing happened, but we'll look at the proper display form:



        displayedForm[expr_] :=
        First@FrontEndExecute@
        ExportPacket[Cell[BoxData@ToBoxes@expr], "PlainText"]

        displayedForm@var
        (* Out: "Null" *)


        So the object is displaying as Null.



        Now we'll assign something new to var:



        var = 1;
        displayedForm@var
        (* Out: "1" *)

        OwnValues@var//InputForm
        (* Out: HoldPattern[var] :> Listener["b"] *)


        And now even though it displays as 1 the value of var hasn't changed at all.



        Next we'll attach a callback that simply tells us what kind of change is being applied to this Listener object:



        var["Callback"]=Print[#]&;
        var=2
        (* Print: Update *)
        (* Out: 2 *)
        var=1
        (* Print: Update *)
        (* Out: 1 *)
        var[[1]] = 5
        (* Print: UpdatePart *)
        (* Out: 5 *)


        This callback is telling us the type of change applied. It's also passed the name ("b" in this case) and the args of the change and if it returns False the change is rejected.



        Now we'll get to the original reason this was made, which is for forcing updated in Dynamic. If we use:



        ListenerDynamic[RandomReal, var]


        every time var is updated the argument (RandomReal) gets re-evaluated.



        Updates to var, while slow relative to standard variable assignment, are still faster than box updates, so we can the classic:



        ListenerDynamic[var = RandomReal, var]


        And watch it tick away. I haven't supported Increment and AddTo and things, so don't get too crazy with it, but it would be easy to add.



        Unlike Dynamic[x = RandomReal], though, we can turn this off by setting: var["Callback"] = False &



        So, great, we got what we came for. We effectively have a Tk *Var* object.



        Implementation



        But what's fun about this is how it's done. I didn't really do anything fundamentally different than my OOP work here but I used a completely different backend that could have been handled in a very different way to very different effect.



        Language`ExpressionStore



        To implement this I used the new Language`ExpressionStore. These are "weak" hash maps in that they store values without messing with the ref-counts of stored object and they use a specific object instance as a key and when that object is destroyed all its associated keys and values in the map are destroyed.



        There's an example of this in the linked question, but we can see how it works here. First we'll make a new object and get the variable it uses for storing state:



        $HistoryLength = 0 (* to prevent ref count changes from Out *)
        obj = Listener["object"];
        obj["Variable"]

        (* Out: Hold[Listeners`Private`listener$21210] *)


        This Symbol is what is doing all the state holding and is what that ListenerDynamic is looking for updates on.



        Now we'll do some memory tests. First we'll create a large array any attach it to the object:



        mem1 = MemoryInUse;

        obj = RandomReal[-1, 1, 100, 100, 100];
        mem2 = MemoryInUse;
        mem2 - mem1

        (* Out: 8000368 *)


        And then we'll simply remove any reference obj as a variable has to its held Listener object in a way where I couldn't have even attached UpValues to mess with things:



        OwnValues[obj] = ;
        mem3 = MemoryInUse;
        mem3 - mem1

        (* Out: -1128 *)


        And we see that all the memory associated with obj has been freed. And if we check the definitions on that storage symbol:



        Listeners`Private`listener$21210

        (* Out: Listeners`Private`listener$21210 *)


        We see it cleaned itself up. This is because it was a Temporary symbol and once there were no references to the Listener object that was its key in the underlying ExpressionStore both of them were cleaned up.



        Language`MutationHandler



        We use ExpressionStore as our backend and then as the interface (front-end?) to the object we use the Language`*Mutation* functions to allow us to cleanly allow var = x to call a custom setter instead of simply calling Set on var.



        System`Private`*Entry*



        I also use the System`Private`*Entry* functions as a way to test whether my objects have truly been "constructed" or not. If we get a raw Listener[...] expression we can check whether System`Private`HoldEntryQ is true or not and if it is we call a constructor that will return a Listener[...] object that looks the same but is a fundamentally different object with System`Private`HoldSetNoEntry called on it.



        Object lookup



        I wanted to be able to just use Listener[name] raw without ensuring I had the variable bound as a convenience, so I actually had to check whether an object with that name had already been built. I did this by searching the keys in the expression store I built (Listeners`Package`$Listeners). This is slightly inefficient, but it only happens in the construction step, so I thought the time hit was worth taking.



        New forms of OOP



        One nice thing about this form of OOP is that it allows us to have more opaque objects than I used in my SymbolObjects package. We could simply use ExpressionStore as a way to store all fields and methods. The memory will be naturally cleaned when whatever object we return from our constructor goes out of scope.



        This is both cleaner and potentially more robust than writing interfaces to Symbol or Association.



        I wouldn't be surprised to see a lot more development from WRI and package developers around here in that vein.






        share|improve this answer






















          up vote
          5
          down vote










          up vote
          5
          down vote









          As part of handling this for myself I wrote a package that does it. The idea was to mimic the *Var() classes in tkinter. I'll do a quick demo then explain how I built this, which is the actually interesting part of this.



          First we'll load that package and make one of these Listener objects:



          Get["https://github.com/b3m2a1/mathematica-tools/raw/master/Listeners.m"]

          var=Listener["b"];
          var//InputForm
          (*Out: Listener["b"] *)


          It looks like nothing happened, but we'll look at the proper display form:



          displayedForm[expr_] :=
          First@FrontEndExecute@
          ExportPacket[Cell[BoxData@ToBoxes@expr], "PlainText"]

          displayedForm@var
          (* Out: "Null" *)


          So the object is displaying as Null.



          Now we'll assign something new to var:



          var = 1;
          displayedForm@var
          (* Out: "1" *)

          OwnValues@var//InputForm
          (* Out: HoldPattern[var] :> Listener["b"] *)


          And now even though it displays as 1 the value of var hasn't changed at all.



          Next we'll attach a callback that simply tells us what kind of change is being applied to this Listener object:



          var["Callback"]=Print[#]&;
          var=2
          (* Print: Update *)
          (* Out: 2 *)
          var=1
          (* Print: Update *)
          (* Out: 1 *)
          var[[1]] = 5
          (* Print: UpdatePart *)
          (* Out: 5 *)


          This callback is telling us the type of change applied. It's also passed the name ("b" in this case) and the args of the change and if it returns False the change is rejected.



          Now we'll get to the original reason this was made, which is for forcing updated in Dynamic. If we use:



          ListenerDynamic[RandomReal, var]


          every time var is updated the argument (RandomReal) gets re-evaluated.



          Updates to var, while slow relative to standard variable assignment, are still faster than box updates, so we can the classic:



          ListenerDynamic[var = RandomReal, var]


          And watch it tick away. I haven't supported Increment and AddTo and things, so don't get too crazy with it, but it would be easy to add.



          Unlike Dynamic[x = RandomReal], though, we can turn this off by setting: var["Callback"] = False &



          So, great, we got what we came for. We effectively have a Tk *Var* object.



          Implementation



          But what's fun about this is how it's done. I didn't really do anything fundamentally different than my OOP work here but I used a completely different backend that could have been handled in a very different way to very different effect.



          Language`ExpressionStore



          To implement this I used the new Language`ExpressionStore. These are "weak" hash maps in that they store values without messing with the ref-counts of stored object and they use a specific object instance as a key and when that object is destroyed all its associated keys and values in the map are destroyed.



          There's an example of this in the linked question, but we can see how it works here. First we'll make a new object and get the variable it uses for storing state:



          $HistoryLength = 0 (* to prevent ref count changes from Out *)
          obj = Listener["object"];
          obj["Variable"]

          (* Out: Hold[Listeners`Private`listener$21210] *)


          This Symbol is what is doing all the state holding and is what that ListenerDynamic is looking for updates on.



          Now we'll do some memory tests. First we'll create a large array any attach it to the object:



          mem1 = MemoryInUse;

          obj = RandomReal[-1, 1, 100, 100, 100];
          mem2 = MemoryInUse;
          mem2 - mem1

          (* Out: 8000368 *)


          And then we'll simply remove any reference obj as a variable has to its held Listener object in a way where I couldn't have even attached UpValues to mess with things:



          OwnValues[obj] = ;
          mem3 = MemoryInUse;
          mem3 - mem1

          (* Out: -1128 *)


          And we see that all the memory associated with obj has been freed. And if we check the definitions on that storage symbol:



          Listeners`Private`listener$21210

          (* Out: Listeners`Private`listener$21210 *)


          We see it cleaned itself up. This is because it was a Temporary symbol and once there were no references to the Listener object that was its key in the underlying ExpressionStore both of them were cleaned up.



          Language`MutationHandler



          We use ExpressionStore as our backend and then as the interface (front-end?) to the object we use the Language`*Mutation* functions to allow us to cleanly allow var = x to call a custom setter instead of simply calling Set on var.



          System`Private`*Entry*



          I also use the System`Private`*Entry* functions as a way to test whether my objects have truly been "constructed" or not. If we get a raw Listener[...] expression we can check whether System`Private`HoldEntryQ is true or not and if it is we call a constructor that will return a Listener[...] object that looks the same but is a fundamentally different object with System`Private`HoldSetNoEntry called on it.



          Object lookup



          I wanted to be able to just use Listener[name] raw without ensuring I had the variable bound as a convenience, so I actually had to check whether an object with that name had already been built. I did this by searching the keys in the expression store I built (Listeners`Package`$Listeners). This is slightly inefficient, but it only happens in the construction step, so I thought the time hit was worth taking.



          New forms of OOP



          One nice thing about this form of OOP is that it allows us to have more opaque objects than I used in my SymbolObjects package. We could simply use ExpressionStore as a way to store all fields and methods. The memory will be naturally cleaned when whatever object we return from our constructor goes out of scope.



          This is both cleaner and potentially more robust than writing interfaces to Symbol or Association.



          I wouldn't be surprised to see a lot more development from WRI and package developers around here in that vein.






          share|improve this answer












          As part of handling this for myself I wrote a package that does it. The idea was to mimic the *Var() classes in tkinter. I'll do a quick demo then explain how I built this, which is the actually interesting part of this.



          First we'll load that package and make one of these Listener objects:



          Get["https://github.com/b3m2a1/mathematica-tools/raw/master/Listeners.m"]

          var=Listener["b"];
          var//InputForm
          (*Out: Listener["b"] *)


          It looks like nothing happened, but we'll look at the proper display form:



          displayedForm[expr_] :=
          First@FrontEndExecute@
          ExportPacket[Cell[BoxData@ToBoxes@expr], "PlainText"]

          displayedForm@var
          (* Out: "Null" *)


          So the object is displaying as Null.



          Now we'll assign something new to var:



          var = 1;
          displayedForm@var
          (* Out: "1" *)

          OwnValues@var//InputForm
          (* Out: HoldPattern[var] :> Listener["b"] *)


          And now even though it displays as 1 the value of var hasn't changed at all.



          Next we'll attach a callback that simply tells us what kind of change is being applied to this Listener object:



          var["Callback"]=Print[#]&;
          var=2
          (* Print: Update *)
          (* Out: 2 *)
          var=1
          (* Print: Update *)
          (* Out: 1 *)
          var[[1]] = 5
          (* Print: UpdatePart *)
          (* Out: 5 *)


          This callback is telling us the type of change applied. It's also passed the name ("b" in this case) and the args of the change and if it returns False the change is rejected.



          Now we'll get to the original reason this was made, which is for forcing updated in Dynamic. If we use:



          ListenerDynamic[RandomReal, var]


          every time var is updated the argument (RandomReal) gets re-evaluated.



          Updates to var, while slow relative to standard variable assignment, are still faster than box updates, so we can the classic:



          ListenerDynamic[var = RandomReal, var]


          And watch it tick away. I haven't supported Increment and AddTo and things, so don't get too crazy with it, but it would be easy to add.



          Unlike Dynamic[x = RandomReal], though, we can turn this off by setting: var["Callback"] = False &



          So, great, we got what we came for. We effectively have a Tk *Var* object.



          Implementation



          But what's fun about this is how it's done. I didn't really do anything fundamentally different than my OOP work here but I used a completely different backend that could have been handled in a very different way to very different effect.



          Language`ExpressionStore



          To implement this I used the new Language`ExpressionStore. These are "weak" hash maps in that they store values without messing with the ref-counts of stored object and they use a specific object instance as a key and when that object is destroyed all its associated keys and values in the map are destroyed.



          There's an example of this in the linked question, but we can see how it works here. First we'll make a new object and get the variable it uses for storing state:



          $HistoryLength = 0 (* to prevent ref count changes from Out *)
          obj = Listener["object"];
          obj["Variable"]

          (* Out: Hold[Listeners`Private`listener$21210] *)


          This Symbol is what is doing all the state holding and is what that ListenerDynamic is looking for updates on.



          Now we'll do some memory tests. First we'll create a large array any attach it to the object:



          mem1 = MemoryInUse;

          obj = RandomReal[-1, 1, 100, 100, 100];
          mem2 = MemoryInUse;
          mem2 - mem1

          (* Out: 8000368 *)


          And then we'll simply remove any reference obj as a variable has to its held Listener object in a way where I couldn't have even attached UpValues to mess with things:



          OwnValues[obj] = ;
          mem3 = MemoryInUse;
          mem3 - mem1

          (* Out: -1128 *)


          And we see that all the memory associated with obj has been freed. And if we check the definitions on that storage symbol:



          Listeners`Private`listener$21210

          (* Out: Listeners`Private`listener$21210 *)


          We see it cleaned itself up. This is because it was a Temporary symbol and once there were no references to the Listener object that was its key in the underlying ExpressionStore both of them were cleaned up.



          Language`MutationHandler



          We use ExpressionStore as our backend and then as the interface (front-end?) to the object we use the Language`*Mutation* functions to allow us to cleanly allow var = x to call a custom setter instead of simply calling Set on var.



          System`Private`*Entry*



          I also use the System`Private`*Entry* functions as a way to test whether my objects have truly been "constructed" or not. If we get a raw Listener[...] expression we can check whether System`Private`HoldEntryQ is true or not and if it is we call a constructor that will return a Listener[...] object that looks the same but is a fundamentally different object with System`Private`HoldSetNoEntry called on it.



          Object lookup



          I wanted to be able to just use Listener[name] raw without ensuring I had the variable bound as a convenience, so I actually had to check whether an object with that name had already been built. I did this by searching the keys in the expression store I built (Listeners`Package`$Listeners). This is slightly inefficient, but it only happens in the construction step, so I thought the time hit was worth taking.



          New forms of OOP



          One nice thing about this form of OOP is that it allows us to have more opaque objects than I used in my SymbolObjects package. We could simply use ExpressionStore as a way to store all fields and methods. The memory will be naturally cleaned when whatever object we return from our constructor goes out of scope.



          This is both cleaner and potentially more robust than writing interfaces to Symbol or Association.



          I wouldn't be surprised to see a lot more development from WRI and package developers around here in that vein.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered 8 hours ago









          b3m2a1

          24k151144




          24k151144




















              up vote
              1
              down vote













              You may use PersistentValue with its ValuePreprocessingFunction option.



              For some listening function such as



              listen = Echo;


              included in a validation function



              validateInteger[x_] := If[IntegerQ[x], listen@x; x, $Failed]


              then a PersistentValue can be created which is validated and notifies updates via the ValuePreprocessingFunction.



              PersistentValue["int", PersistenceLocation["KernelSession"], 
              ValuePreprocessingFunction -> validateInteger] = 0;



              » 0




              When set to a value that validates the listen function is called and the PersistentValue updated.



              PersistentValue["int", "KernelSession"] = 6



              » 6



              6



              When set to a value that does not validate the listen function is not called and the PersistentValue is not updated.



              PersistentValue["int", "KernelSession"] = 6.5



              $Failed



              PersistentValue["int", "KernelSession"]



              6



              Clean up with



              DeleteObject[PersistentObjects["int"]];


              Hope this helps.






              share|improve this answer
























                up vote
                1
                down vote













                You may use PersistentValue with its ValuePreprocessingFunction option.



                For some listening function such as



                listen = Echo;


                included in a validation function



                validateInteger[x_] := If[IntegerQ[x], listen@x; x, $Failed]


                then a PersistentValue can be created which is validated and notifies updates via the ValuePreprocessingFunction.



                PersistentValue["int", PersistenceLocation["KernelSession"], 
                ValuePreprocessingFunction -> validateInteger] = 0;



                » 0




                When set to a value that validates the listen function is called and the PersistentValue updated.



                PersistentValue["int", "KernelSession"] = 6



                » 6



                6



                When set to a value that does not validate the listen function is not called and the PersistentValue is not updated.



                PersistentValue["int", "KernelSession"] = 6.5



                $Failed



                PersistentValue["int", "KernelSession"]



                6



                Clean up with



                DeleteObject[PersistentObjects["int"]];


                Hope this helps.






                share|improve this answer






















                  up vote
                  1
                  down vote










                  up vote
                  1
                  down vote









                  You may use PersistentValue with its ValuePreprocessingFunction option.



                  For some listening function such as



                  listen = Echo;


                  included in a validation function



                  validateInteger[x_] := If[IntegerQ[x], listen@x; x, $Failed]


                  then a PersistentValue can be created which is validated and notifies updates via the ValuePreprocessingFunction.



                  PersistentValue["int", PersistenceLocation["KernelSession"], 
                  ValuePreprocessingFunction -> validateInteger] = 0;



                  » 0




                  When set to a value that validates the listen function is called and the PersistentValue updated.



                  PersistentValue["int", "KernelSession"] = 6



                  » 6



                  6



                  When set to a value that does not validate the listen function is not called and the PersistentValue is not updated.



                  PersistentValue["int", "KernelSession"] = 6.5



                  $Failed



                  PersistentValue["int", "KernelSession"]



                  6



                  Clean up with



                  DeleteObject[PersistentObjects["int"]];


                  Hope this helps.






                  share|improve this answer












                  You may use PersistentValue with its ValuePreprocessingFunction option.



                  For some listening function such as



                  listen = Echo;


                  included in a validation function



                  validateInteger[x_] := If[IntegerQ[x], listen@x; x, $Failed]


                  then a PersistentValue can be created which is validated and notifies updates via the ValuePreprocessingFunction.



                  PersistentValue["int", PersistenceLocation["KernelSession"], 
                  ValuePreprocessingFunction -> validateInteger] = 0;



                  » 0




                  When set to a value that validates the listen function is called and the PersistentValue updated.



                  PersistentValue["int", "KernelSession"] = 6



                  » 6



                  6



                  When set to a value that does not validate the listen function is not called and the PersistentValue is not updated.



                  PersistentValue["int", "KernelSession"] = 6.5



                  $Failed



                  PersistentValue["int", "KernelSession"]



                  6



                  Clean up with



                  DeleteObject[PersistentObjects["int"]];


                  Hope this helps.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered 16 mins ago









                  Edmund

                  24.6k32996




                  24.6k32996



























                       

                      draft saved


                      draft discarded















































                       


                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function ()
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fmathematica.stackexchange.com%2fquestions%2f182617%2fhow-can-i-implement-a-listener-variable-in-mathematica%23new-answer', 'question_page');

                      );

                      Post as a guest













































































                      Comments

                      Popular posts from this blog

                      What does second last employer means? [closed]

                      Installing NextGIS Connect into QGIS 3?

                      One-line joke