# demand.tcl -- # "Demand-driven" computation # package require Tcl 8.4 ;# Advanced [lindex] namespace eval ::DemandDriven { } # compute -- # Register the variables and the associated computations # Arguments: # list List of variable-expression pairs # Result: # None # Side effects: # The variables are defined in global namespace and # there are read traces attached to them # proc ::DemandDriven::compute {list} { foreach {v expr} $list { global $v trace add variable ::$v read [list ::DemandDriven::GetValue $expr] } } # GetValue -- # Get the value of the variable # Arguments: # expr The expression by which to get the value # name Name of the variable (it is global!) # dummy Dummy in this context # op Operation (dummy too) # Result: # None # Side effects: # The variable is set # proc ::DemandDriven::GetValue {expr name dummy op} { # # We need to take more care: traces are not nested ... if { ! [info exists ::$name] } { set count 0 while { [catch { uplevel #0 [string map [list NAME $name EXPR $expr] {set ::NAME [expr {EXPR}]}] } msg] } { if { [regexp {can't read "(.*)": no} $msg ==> varname] } { uplevel #0 [list set ::$varname \ "\[::DemandDriven::GetValue [list [lindex [trace info variable ::$varname] 0 1]] $varname {} read\]"] incr count } else { puts $errorInfo break } if { $count > 3 } { break } } } } # ask -- # Print a question and ask for the answer # Arguments: # question The question to be posed # Result: # Whatever the user types # proc ask {question} { puts -nonewline $question flush stdout gets stdin } # main -- # Test this idea # ::DemandDriven::compute { volume {$sphere? $volumeSphere : $volumeCube} sphere {[ask "Is it a sphere (1) or a cube (0)? "]} volumeSphere {4.0*$pi/3.0*pow($rad,3)} volumeCube {pow($side,3)} pi 3.1415926 side {[ask "The side of the cube: "]} rad {[ask "The radius of the sphere: "]} } puts "Compute the volume of a sphere or cube:" puts "Volume: $volume"
NEM: Very nice! Mozart/Oz has the concept of a dataflow variable which is essentially very similar to this. A dataflow variable is like a logic variable in Prolog in that it is initially unbound and once given a value it keeps it (i.e., it is not mutable). However, dataflow variables are also used as a communication/condition synchronisation mechanism between threads: if one thread tries to get the value of an unbound variable it will suspend until another thread binds the variable (through unification). This allows for declarative programming of concurrency (see Concurrency concepts), and is really a very interesting technique as it really takes a lot of the complexity out of multi-threaded programming. The BOOK Concepts, Techniques, and Models of Computer Programming has much more on the subject.HJG I get 'Error: can't read "volume": can't read "errorInfo": no such variable'. Maybe this uses a Tcl 8.5 feature ?AM That is strange ... I use a Tcl 8.4 feature (lindex with two indices) but that is all. Are you using tclsh ? Wish on Windows will have a severe problem - [gets] is not working from a console. RS: Have a look at the gets workaround :)In a wish console on Windows I get: % source demand.tcl Is it a sphere (1) or a cube (0)? can't read "volume": can't read "errorInfo": no such variable (tmp) 2 %This brings to CL's mind, along with dataflow variables, inferencing in expert systems, generally under the rubric of "forward [or backward] chaining".