Version 3 of Thingy: a one-liner OO system

Updated 2001-10-11 11:46:55

Clipped from the Tcl chatroom on Feb 5, 2001:

suchenwi: This weekend I was thinking about minimal OO and came to the following one-liner:

 proc thingy name {proc $name args "namespace eval $name \$args"} 

suchenwi: This is very poor, no inheritance at all. It just sugars the namespace wrapping, which in turn allows instance "variables" and "methods".

 thingy foo        
 foo set bar 1                ;#== set foo::bar 1
 foo proc grill x {subst $x!} ;#== proc foo::grill x {subst $x!}
 foo grill sausages           ;#== foo::grill sausages

miguel: Not bad at all! It does have the nice property that properties and methods *are* regular tcl vars and procs.

suchenwi: And, as I just tried, it can be nested:

 foo thingy baz 
 foo baz set id 42            ;#== set foo::baz::id 42
 foo baz proc wow x {subst x?} 

rmax: seems to be related to this one: Namespace resolution on unknown command

suchenwi: rmax: Exactly. At that time, Larry Smith proposed that feature for the Tcl core, but like often, you can do it yourself - in a one-liner, in this case...

rmax: suchenwi: I prefer to write "proc foo baz wow {x} {subst x?}" It is more readable IMHO.

suchenwi: Of course you can do it like this. But for the parser, {x} is "x" is x. YM foo baz proc wow {x} {subst $x?}

rmax: Yes, I know that x == {x} . My point was, that the semantic of "proc foo baz wow {} {...}" is more obvious than "foo baz proc wow {} {...}"

suchenwi: Oh. Yes, I just reread that Wiki page. The advantage of the above plaything is minimal effort, and orthogonality e.g. with "set", where "set foo bar" could not be taken as retrieving the value of foo::bar...

suchenwi: Methods could be dynamically shared like in UFO; variables via upvar.

miguel: On closer looks, it is (almost) perfect; good thinking, RS! Inheritance will pose some challenges though:

  • (a) inheritable procs will need to have the object pointer passed to them ("self/this/@")
  • (a') inheritable procs should refer to everything by global name
  • (b) inheriting a proc means copying it (as with waytos ...); what with compiled commands?

miguel: As it is, you can send not only single commands but also longer scripts (commands separated by ';' and properly quoted). If you restrict yourself to single commands, the following variante should be faster:

miguel: Well (back to thingys): it is faster, but less elegant:

 proc thingy name {
     namespace eval $name
     proc $name args "namespace inscope $name \$args"
 } 

miguel: It only works for single commands, but uses the faster eval on pure lists (I think, haven't timed it ...)


rmax: This adds copying of prototype objects:

 proc new {name} {

    uplevel 1 thingy $name

    set current [uplevel 1 namespace current]
    set parent [uplevel 1 namespace parent]
    foreach variable [info vars ${current}::*] {
        set vars $variable
        if {[array exists $variable]} {
            set vars [list]
            foreach n [array names $variable] {
                lappend vars ${variable}($n)
            }
        }
        foreach var $vars {
            namespace eval ${parent}::$name \
                [list set [lindex [split $var ::] end] [set $var]]
        }
    }
    foreach cmd [info procs ${current}::*] {

        set arglist [list]
        foreach arg [info args $cmd] {
            if {[info default $cmd $arg def]} {

                set arg [list $arg $def]
            }
            lappend arglist $arg
        }
        namespace eval ${parent}::${name} \
            [list proc [lindex [split $cmd ::] end] \
                $arglist [info body $cmd]]
    }
 }

rmax: I think, I just found a bug in your oneliner from this morning: it places all thingies (even the nested ones) in the global namespace.

rmax: here is one, that nests correctly:

 proc thingy name {
   proc [uplevel 1 namespace current]::$name args \
      "namespace eval $name \$args"
 }

suchenwi: Ah, I see. YM if the caller is in some namespace himself.

rmax: OK, the namespaces are nested as expected but the procs are all in the global namespace.

rmax: ... so you can't have "foo proc bar" and "baz proc bar" at the same time.

suchenwi: I can! The original one-liner that started this discussion creates ::foo::grill and ::bar::grill which are independent.

rmax:

 proc thingy name {proc $name args "namespace eval $name \$args"} 
 thingy foo 
 foo thingy foo1 
 puts [info commands foo*] => foo foo1 

suchenwi: Yes, I see.

rmax: namespace children :: foo* => foo foo1


Object orientation - Arts and crafts of Tcl-Tk programming