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 $canv coords $id]] } 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.