neo

neo is for namespace ensemble objects - see NEM's Using namespace ensemble without a namespace (in fact, it uses ::). RS hacked together this little candybox as a variation on that page - yet another toy OO:

namespace eval neo {variable version 0.1}

proc neo::new {name _where_ args} {
    set map [dict create]
    foreach {slot = value} $args { dict set map $slot $value }
    namespace ensemble create -command $name -map $map
}
proc neo::slot {object slot = command args} {
    set map [config $object -map]
    dict set map $slot [linsert $args 0 $command]
    config $object -map $map
}
proc neo::method {object name params body} {
    set params [linsert $params 0 self]
    slot $object $name = ::apply [list $params $body ::] $object
}
proc neo::const val { return $val }

interp alias {} neo::config {} namespace ensemble configure

proc neo::delete name {rename $name {}}

#-- So far for neo the "system", the rest is tests and demos:

proc rect {canv x0 y0 x1 y1} { #-- a constructor
    set id [$canv create rect $x0 $y0 $x1 $y1]
    set obj [neo::new $canv.rect$id where \
        id     = [list const $id] \
        coords = [list ::$canv coords $id]]
    neo::slot $obj canvas = const $canv
    set obj
}
catch {destroy .c} ;# good for repeated sourcing
pack [canvas .c]
set r [rect .c 20 20 100 100]
puts "id = [$r id], coords = [$r coords]"

neo::slot $r type = const "rectangle" ;#-- object-specific slot
puts "$r is a [$r type]"

neo::method $r width {} {
    lassign [$self coords] x0 y0 x1 y1
    expr {abs($x1-$x0)}
}
neo::method $r height {} {
    lassign [$self coords] x0 y0 x1 y1
    expr {abs($y1-$y0)}
}
puts height:[$r height],width:[$r width]

neo::method $r area {} { expr {[$self width] * [$self height]} }
puts "area = [$r area]"
puts [neo::config $r -map]

RS So how are ensembles suitable for representing objects?

  • one object corresponds to one command
  • The popular $obj method arg... calling style is directly supported
  • dispatching and introspection are built in (in fast C code)
  • objects are easily serialized by lugging the map around (reminiscent of TOOT, also by NEM)
  • variables and methods are unified, as if we had no variables :) Getters go by name (e.g. [$self type]), setters just use neo::slot, e.g neo::slot $self type = const square
  • inheritance isn't supported yet, but that could be added (to the map)

All in all, neos are very simple yet powerful - and I like that :^)


MS proposes a small modification to ::neo::new, that allows the class methods (slot, method, config, delete) to be available in oo manner. The first modification is taking it out of the ::neo namespace (as it does not work properly as a class method), the second is to define an unknown handler that defines the procs in ::neo as object methods:

proc neo {name _where_ args} {
    set map [dict create]
    foreach {slot = value} $args { dict set map $slot $value }
    namespace ensemble create -command $name -map $map \
        -unknown {::apply {{obj cmd args} {list ::neo::$cmd $obj}}}
}

he novelty in here is that we can do e.g.
% neo test w
::test
% test method shout {} {puts "This is $self"}
% test shout
This is ::test
% test scream
invalid command name "::neo::scream"
% proc ::neo::scream obj {puts "THIS IS $obj"}
% test scream
THIS IS ::test
% test method scream {} {puts "$self does not scream"}
% test scream
::test does not scream
% test configure
invalid command name "::neo::configure"
% test config
-map {scream {::apply {self {puts "$self does not scream"} ::} ::test} shout {::apply {self {puts "This is $self"} ::} ::test}} -namespace :: -prefixes 1 -subcommands {} -unknown {::apply {{obj cmd args} {list ::neo::$cmd $obj}}}

See also eos for a closely related system.