Version 20 of Tiny OO with Jim

Updated 2007-06-28 15:09:15 by MiR

if 0 {Richard Suchenwirth 2005-03-16 - Here's a little experiment in OO, using the Jim feature that procs and lambdas can have a "closure" (a set of static variables) associated.

(Note that this code does normally not run on regular Tcl interpreters - use Jim instead - or Jimulation!)

This way, the only physical location of an object (the closure with its instance variables) is its lambda. No namespaces needed (or possible in Jim ;^) Jim's lambdas are just procs with a generated name, but they will be garbage-collected when no longer used.

Here's a "constructor" the hard (bare-bones) way:}

 proc Account args {
    lambda {method args} {{balance 0}} {eval Account'$method $args}
 }

if 0 {Alternatively, if you want to use more than one class, here's factoring out the generic part. It also adds an introspection method for the names of instance vars. Other methods that all classes shall have, can be placed here too:}

 proc class {name instvars} {
    proc $name args \
       [list lambda {method args} $instvars "eval $name'\$method \$args"]
    proc $name'vars {} [list return [lmap i $instvars {lindex $i 0}]]
 }

#-- Let's re-create a first class, the usual bank account, with one instance variable:

 class Account {
    {balance 0}
 }

#-- Bare-bone methods are procs with class'method names. They need to upvar the instance vars they use:

 proc Account'deposit amount {
    upvar 1 balance balance
    set balance [+ $balance $amount]
 }

#-- Here's a wrapper to implicitly do those upvars in methods:

 proc method {class name argl body} {
   proc $class'$name $argl "foreach i \[\[self] vars] {upvar 1 \$i \$i} \n$body"
 }

#-- Methods can be written much slicker now:

 method Account see {} {set balance}

 method Account withdraw amount {
    if {($balance-$amount) < 0} {error "can only withdraw $balance"}
    set balance [- $balance $amount]
 }

if 0 {"Who am I?" is a deep philosophical question. Inside methods, it's easily answered - the name of the caller's caller, i.e. the object lambda:}

 proc self {} {lindex [info level -2] 0}

if 0 {Now testing:

 set a [Account]
 puts a:$a
 $a deposit 100
 puts "deposit 100 -> [$a see]"
 $a withdraw 40
 catch {$a withdraw 1000} res
 puts $res

shows on stdout

 a:<reference.<functio>.00000000000000000000>
 deposit 100 -> 100
 can only withdraw 60

Conclusion: For years, I wondered what exactly closures are. After seeing their simplicity and power in this example (9 LOC for class-based OO without inheritance - for the convenience wrappers class, method, and self; if you use only "bare-bones" constructors and methods as shown above, you need zero LOC of framework :-), I strongly advocate that Tcl adopts them too, for procs. And we can have it today, with Jimulation :)


SS Very nice! It seems to me a very natural hack to try to implement objects via closures. Also your way to do the dispatch is impressive ;)


MiR: Question: Is there any way to do inheritance with closures?


Arts and crafts of Tcl-Tk programming | Category Object Orientation | Category Jim }