Version 0 of Demand-driven computation

Updated 2005-12-21 09:03:07 by AM

Arjen Markus (21 december 2005) Spreadsheets differ in the way they perform their computation from ordinary programs (in Tcl or Fortran or most other languages): you do not need to specifiy the order of the computations. Somehow they determine that themselves: you can refer to cell Z100 in the formula for cell A1, for instance.

Can we do the same? Yes, we can.

It is even possible without analysing the formulae for references to other variables.

The script below defines a simple set of computations, one for each variable. Then it prints the variable "volume" - without it having been set before....

Run it and you will see what happens!

We need a buzzword for this paradigm, so that is why I called the page Demand-driven computation or DDC. (But seriously, you could do interesting things this way)


 # demand.tcl --
 #     "Demand-driven" computation
 #
 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"

[ [Category ???] ]