Version 1 of Using namespace ensemble without a namespace

Updated 2006-10-06 12:29:33 by dkf

NEM 6 Oct 2006: While contemplating all things OO and TIP 257 [L1 ], I came across an interesting interaction between features available in Tcl 8.5. It is possible to use namespace ensemble without needing any namespace to back it up! In particular, the -map option can be used to model simple slot-based objects. Together with the apply command, methods can then be added. To demonstrate, let's consider writing a simple stateless object to represent a rectangle on a canvas:

 proc rect {canv x0 y0 x1 y1} {
     set id [$canv create rect $x0 $y0 $x1 $y1]
     namespace ensemble create -command $canv.rect$id \
         -map [dict create id      [list const $id] \
                           coords  [list const [list $x0 $y0 $x1 $y1]]]
 }
 proc const val { return $val }

We now have a simple constructor that creates a rectangle on a canvas and creates an object command for that rectangle, with two slots to get the canvas id and coordinates associated with the rectangle:

 pack [canvas .c]
 set r [rect .c 20 20 100 100]
 puts "id = [$r id], coords = [$r coords]"

These slots are just entries in the -map of the ensemble that alias to the "const" command to simply return their value. Note that no namespace is involved at all! (DKF: Actually, the namespace involved is the global namespace, but getting rid of that is equivalent to deleting the interpreter.)

We can write a general method for creating slot aliases on these objects:

 proc alias {object slot command args} {
     set map [namespace ensemble configure $object -map]
     dict set map $slot [linsert $args 0 $command]
     namespace ensemble configure $object -map $map
 }

We can now add extra slots to the rectangle object:

 alias $r type const "rectangle"
 puts "$r is a [$r type]"

Using anonymous functions via apply in 8.5, we can even add new methods to the object. These are simply aliases to an anonymous function. We add a "self" parameter and arrange for it to be filled with the object command:

 proc method {object name params body} {
     set params [linsert $params 0 self]
     alias $object $name ::apply [list $params $body ::] $object
 }
 method $r width {} {
     lassign [$self coords] x0 y0 x1 y1
     expr {abs($x1-$x0)}
 }
 method $r height {} {
     lassign [$self coords] x0 y0 x1 y1
     expr {abs($y1-$y0)}
 }

We can even add method and alias as methods on the object itself:

 alias $r method ::method $r
 alias $r alias  ::alias $r

 $r method area {} { expr {[$self width] * [$self height]} }
 puts "area = [$r area]"

We could add mutable slots by creating a slot alias that can rewrite itself. This seems to be a fascinating new way of creating relatively lightweight objects, benefiting from the fast namespace ensemble mechanism, without having the overhead of an actual namespace.


[Category Object Orientation|Category Example]