***Reactive Programming Infrastructure for Tcl*** [CGM] : Reactive Programming is a technique where the programmer specifies a set of invariant relationships between the inputs and outputs of a system.  The system then reacts to changes in the inputs by automatically updating the outputs to maintain these relationships.  Automatic tracking of the interdependencies of these computations allows them to be scheduled when required but not otherwise.  This can be viewed as a generalisation of the operation of spreadsheets to support more free-form and complex computations. The code presented here is an attempt to provide a simple and lightweight framework to support reactive programming in Tcl. It defines a TclOO class "Reactive" whose objects represent reactive variables, with an alias `react` so we can create such an object with just: `react myVar` Calling the object with no arguments will return its value, or an error if that has not been defined. Such a variable can be given a fixed value: `react myStr == {a b c}` or defined with an expression: `react myNum = {3 * 2}` or defined by a chunk of code to evaluate: `react myResult <- {string repeat [[myStr]] [[myNum]]}` Such calculated values are ''memoized'' to avoid recalculating them unnecessarily. However the Reactive objects also keep track of the dependencies between them, discarding the memoized values when any of the inputs from which they were calculated is updated. So if we modify `myNum` by doing: `myNum = 10` the memoized value of `myResult` will be discarded, and the next time it is requested it will be recalculated. These are trivial examples - this technique only becomes really useful when the computations being memoized are complex and slow enough that it's worth this extra complication to avoid re-doing them unnecessarily. ****ToDo**** * Realistic example of when this approach can pay off. * Extension to arrays or dicts of values, memoizing individual elements. * Mechanism to link input and output Reactive variables to the -textvariable of Tk widgets. ****Code**** ====== # ReacTcl - Reactive Programming Infrastructure for Tcl # See https://en.wikipedia.org/wiki/Reactive_programming - here we use # invalidity notification propagation and a dynamic dependency graph. # In Tcl8.7 reactcl_target can become a classvariable in Reactive set reactcl_target {} # A Reactive object represents a variable which can either be set to a # specific value or computed from other Reactive objects. Such computed # values are memoized and their dependencies on other Reactive values are # automatically tracked so that these computations are re-run only when # changes to their inputs have invalidated the currently memoized output # value. oo::class create Reactive { variable value script ret_code version targets_vers constructor args { set value {} set script {}; # run this to (re)calculate value when required. set ret_code {}; # return code from running script, # or empty when value is unknown, # or code 4 used to detect circular dependencies. set version 0; # incremented when previous value becomes invalid. set targets_vers {}; # record the Reactive objects whose values were # computed from this one, and their versions. # Support appending the definition to the creation of the object: if {[llength $args]} {[self] {*}$args} } method set val { my code {} set value $val set ret_code 0 } method unset {} { my code {} } method code code { set script $code my invalidate } method expr expr { my code [list expr $expr] } method get {} { global reactcl_target set prev_target $reactcl_target set reactcl_target [list [self] $version] try { # Do sanity check and (re)calculate if needed switch $ret_code { {} {set ret_code 4; # For cycle check if {$script eq {}} {error "[self] is unset."} # Evaluate the script, catching errors set ret_code [catch $script value] } 4 {error "[self]: Circular dependency"} } } finally { # Record the target value (if any) that we are contributing to set reactcl_target $prev_target foreach {target ver} $reactcl_target { dict set targets_vers $target $ver } } if {$ret_code == 1} {error $value } return $value } method valid {} { return [expr {$ret_code == 0}] } method invalidate {} { set ret_code {} set value {} incr version dict for {target ver} $targets_vers { $target invalidate_target $ver } set targets_vers {} } method invalidate_target ver { if {$ver == $version && $ret_code in {0 1}} { my invalidate } } # Show internal state for debugging method show {} { return "[self] value='$value' script='$script' ret_code='$ret_code' version='$version' targets_vers='$targets_vers'" } # Hack so we can call with no arguments or abbreviated args method unknown args { lassign $args op val switch $op { {} {return [my get]} == {return [my set $val]} = {return [my expr $val]} <- {return [my code $val]} ! {my invalidate} ? {return [my valid]} default {error "[self]: unknown method '$args'"} } } } interp alias {} react {} Reactive create ====== [APN] Nice. Why not just define methods called == etc. or forward instead of unknown? Also, could this wrap trace so any variable could be included in the framework? [CGM] Thanks. Well I wanted to have the bare object name return its value and you can only do that via `unknown` since you can't define a method with no name (I think), but the other methods could be defined more directly, might change that. Yes, using `trace` to hook this up to ordinary variables is on my ToDo list above, not done yet.