Tao Data Encapsulation

TAO 9.0 no longer encapsulates data. It makes a copy of each variable for the local method call, but changes are not reflected in the state of the object automatically. Essentially all changes in state now have to go through cset.

Why did I do this? Well, I was not getting consistently predictable results when a method could call another method. Anyone overly curious can see the Tao test suite for a breakdown on exactly what was going on. The flipside is that the copy only mechanism didn't end up breaking any existing Tao code. And it reduced the data encapsulation system to a simple loop:

   ::tao::class foo {
          static bat bing
          method bar {} {
                return $bat
          }
    }

    set obj [::tao::object_create #auto {class foo}]
   ## Magical incantation to introspect body of TclOO method ##
    ....start of body ...
    set this [self]
    set statevar [::tao::object_dict $this]

    dict for {var val} [dict get [set $statevar] vardata] { set $var $val } 

    # END Preamble
    return $bat

     ...end of body...

This simple loop is MUCH faster. It also does not interfere with any calls to "variable" from folks mixing Tao code with TclOO

TAO 8.5 uses the "dict with" mechanism for data encapsulation. (Boring eh?) Each object has a global variable, and recursion is handled by a stack. If you were to peer inside of a compiled class:

   ::tao::class foo {
          variable bat bing
          method bar {} {
                return $bing
          }
    }

    set obj [::tao::new foo #auto]
    info body [::tao::class_nspace foo]::bar 
    ....start of body ...

    set this [::tao::opeek]
    set statevar [::tao::object_dict $
    dict with $statevar {
          return $bing
     }

     ...end of body...

TAO 7.x uses a novel techique to handle data encapsulation, a "scope" command that lets one carry data around in bundles and unpack it on demand.

Example:

  scope ::myobj add color variable
  scope ::myobj add type  static myobj
  scope ::myobj add anArray hash

   proc testSet newcolor { 
      scope load ::myobj
      set color $newcolor
   }
   proc testGet {} {
      scope load ::myobj
      return $color
  }
  # Will TRY to reprogram a static value
  # does not throw an error, but neither does it change
  # the state of the encapsulation
  proc testStaticSet ignoredVal { 
      scope load ::myobj
      set type $ignoredVal
   }
  proc testStaticGet {} { 
      scope load ::myobj
      set type $type
   }


   proc testArraySet {var val} { 
      scope load ::myobj
      set anArray($var) $val
   }
   proc testArrayGet {var} { 
      scope load ::myobj
      return [lindex [array get anArray $var] 1]
   }
   
  testSet green
  testGet
  > green
  testSet blue
  testGet
  > blue
  testArraySet height 20
  testArrayGet height
  > 20
  testStaticSet garbage
  # note that changes to "static" vars are not retained
  # this is actually correct
  testStaticGet 
  > myobj

Implementation

There are a few pseudocode callouts that are required. One is "object_array", which maps an object name to a global variable. The others are 'get' which returns a value if it exists, or {} otherwise, ladd which adds a value to a list only if it is not present, and ldelete which removes an element from a list.

    proc scope {cmnd handle {varname {}} {type variable} {value NULL}} {
        upvar #0 [object_array $handle] state
        switch $cmnd {
            load { uplevel 1 [set state(script)] }
            add - del {

                array set vartypes [get state(info)]
                foreach vtype [array names vartypes] { 
                    ldelete vartypes($vtype) $varname
                }
                if { $cmnd == "add" } {
                    ladd vartypes($type) $varname

                    if { $value != "NULL" } { 
                        set state($varname) $value
                    }
                }
                set state(info) [array get vartypes]
                scope rebuild $handle
            }
            rebuild {
                set statevar [object_array $handle]
                upvar #0 $statevar state

                array set meta [get state(info)]
                set script {}

                ###
                #  this is really only for backward compadibility
                #  with classes generated for itcl
                #  enlightened clients will just call objdata(varname)
                #  directly
                ###
                
                append script \n "\# LOAD ENCAPSULATED DATA"
                append script \n [list set statevar $statevar]
                set varlist {}
                lappend varlist  ${statevar} objdata
                set usedvarnames {}        
                set statics [get meta(static)]
                
                foreach {var} [get meta(hash)] {
                    if { [lsearch $usedvarnames $var] >= 0 } continue
                    ladd usedvarnames $var
                    lappend varlist ${statevar}.${var} $var
                }
                
                foreach {var} [get meta(variable)] {
                    if { [lsearch $usedvarnames $var] >= 0 } continue
                    ladd usedvarnames $var
                    lappend varlist ${statevar}($var) $var
                }
                foreach {globvar localvar} $varlist {
                    if { [lsearch $statics $localvar] < 0 } { 
                        append script \n [list catch [list upvar #0 $globvar $localvar]]
                    }
                }
                foreach {var} $statics {
                    append script \n [list set $var [get ${statevar}($var)]]
                }
                set state(script) $script
            }
        }
    }