How can I implement a listener variable in Mathematica?
Clash Royale CLAN TAG#URR8PPP
up vote
3
down vote
favorite
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
add a comment |Â
up vote
3
down vote
favorite
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
Do you need this functionality contained in theManipulate
or is something external to theManipulate
updating the variable? If external then is it in the sameModule
`DynamicModule` or elsewhere?
â Edmund
3 hours ago
@Edmund I was imagining this would be a pure kernel thing without reference toManipulate
orDynamic
(although usable by the latter).
â b3m2a1
2 hours ago
add a comment |Â
up vote
3
down vote
favorite
up vote
3
down vote
favorite
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
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
front-end customization symbols
edited 8 hours ago
asked 8 hours ago
b3m2a1
24k151144
24k151144
Do you need this functionality contained in theManipulate
or is something external to theManipulate
updating the variable? If external then is it in the sameModule
`DynamicModule` or elsewhere?
â Edmund
3 hours ago
@Edmund I was imagining this would be a pure kernel thing without reference toManipulate
orDynamic
(although usable by the latter).
â b3m2a1
2 hours ago
add a comment |Â
Do you need this functionality contained in theManipulate
or is something external to theManipulate
updating the variable? If external then is it in the sameModule
`DynamicModule` or elsewhere?
â Edmund
3 hours ago
@Edmund I was imagining this would be a pure kernel thing without reference toManipulate
orDynamic
(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
add a comment |Â
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.
add a comment |Â
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.
add a comment |Â
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.
add a comment |Â
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.
add a comment |Â
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.
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.
answered 8 hours ago
b3m2a1
24k151144
24k151144
add a comment |Â
add a comment |Â
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.
add a comment |Â
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.
add a comment |Â
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.
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.
answered 16 mins ago
Edmund
24.6k32996
24.6k32996
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Do you need this functionality contained in the
Manipulate
or is something external to theManipulate
updating the variable? If external then is it in the sameModule
`DynamicModule` or elsewhere?â Edmund
3 hours ago
@Edmund I was imagining this would be a pure kernel thing without reference to
Manipulate
orDynamic
(although usable by the latter).â b3m2a1
2 hours ago