Richard Suchenwirth - Things (something like objects, or classes) change. In On things, a dirt simple OO API was described, and an initial implementation in Doing things. It worked, but didn't satisfy me. So I decided to do it more orthogonally - which involved slight (and not bad) changes in the API, e.g.
thing new human ;# instead of: thing human human new Socrates ;# instead of: thing Socrates is-a human (*) thing names ;# instead of: thing -names Socrates legs ;# equivalent to: Socrates set legs Socrates which legs ;# new: tells where a property/way came from Socrates ;# new: returns a pairlist of all properties Socrates clone Diogenes ;# makes an identical thing, except for name
and with namespaces (after all, those were introduced for OO). Now ::thing holds the whole system. For a thing foo, a sub-namespace ::thing::foo is created (and has to be explicitly deleted). Basic ways (methods) are introduced for an initial thing ::thing::thing and inherited by all other things if not overridden. Ways are implemented as namespace procs (so I need not treat default arguments, args myself.. - and they are compiled), but can still be thrown around like real lambdas:
[philosopher new Plato] wayto sing [Socrates wayto sing]
Each way receives the thing's name as first argument (might be called "self", or "-" if ignored).
(*) Note on the is-a list: This is every thing's backbone, take care not to break it! A usable is-a list starts with the thing's name, then possibly has the superthings, and finally the thing 'thing'':
T2 set is-a {T2 SmartToaster Toaster thing}
Thanks to Miguel Sofer for pointing out!
So here's the current (and still pretty minimal) framework for Things:
catch {namespace delete ::thing} ;# good for repeated sourcing in tests namespace eval thing { variable names [list] ;# initially, no things around proc dispatch {name {way ""} args} { # This is the core of the "things" engine foreach i [set ${name}::is-a] { if [llength [info command ${i}::$way]] { return [eval ${i}::$way $name $args] } if [info exists ${i}::$way] {return [set ${i}::$way]} } error "$way? Use one of: [join [Info $name command] {, }]" } # ----------------------------- some helpers for introspection proc Info {name what} { # retrieve all own and inherited procs/properties of 'name' set res [list] foreach i [set ${name}::is-a] { foreach j [info $what ::thing::${i}::*] { regsub ::thing::${i}:: $j "" j2 ladd res $j2 } } lsort $res } proc lambda {name way} { # retrieve [list argl body] for way of thing name foreach i [set ${name}::is-a] { if [llength [set proc [info command ${i}::$way]]] { set res "{"; set space "" foreach i [info args $proc] { if [info default $proc $i value] { append res "$space{$i [list $value]}" } else {append res "$space$i"} set space " " } return [append res "} {[info body $proc]}"] } } error "$way? No way for $name" } }
Now we create and instrument the initial thing, but before that we have to create a way how to create (constructor, some call it):
namespace eval thing::thing { proc new {self name args} { # way to create a new thing 'name' that is-a 'self' if [llength [info command $name]] { error "can't create thing $name: command exists" } if [llength $self] { set t [concat $name [set ::thing::${self}::is-a]] } else { set t $name } namespace eval ::thing::$name variable is-a [list $t] regsub @name {uplevel 1 thing::dispatch @name $args} $name body proc ::$name args $body ;#--------- so it can be called by name regsub @name {rename @name "" ;#} $name trace trace add variable ::thing::${name}::is-a unset $trace lappend ::thing::names $name foreach {key value} $args {$name set $key $value} ::set name } new {} thing ;# ----------------- first "thing" to do proc clone {self name args} { $self new $name foreach {key value} [concat [$self] $args] { if {$key!="is-a"} {$name set $key $value} } namespace eval ::thing::${name} { ::set is-a [lreplace ${is-a} 1 1] } ::set name } proc {} {self} { # empty way: pairlist of all property names and values ::set res [list] foreach i [lsort [info var ::thing::${self}::*]] { regsub ::thing::${self}:: $i "" i2 lappend res $i2 [::set $i] } ::set res } proc set {self {name ""} args} { # way to set, retrieve, or list properties if {$name==""} {return [::thing::Info $self vars]} switch [llength $args] { 0 {} 1 {::set ::thing::${self}::$name [lindex $args 0]} default {error "Usage: $self set ?name ?value??"} } if [catch {::thing::dispatch $self $name} res] { error "$name? No such property for $self" } ::set res } proc unset {self args} { foreach i $args {::unset ::thing::${self}::$i} } proc delete {self} { lremove ::thing::names $self namespace delete ::thing::$self } proc wayto {self {way _None_} args} { # way to define a, retrieve a, or list every way available if {$way=="_None_"} {return [::thing::Info $self command]} switch [llength $args] { 0 {return [::thing::lambda $self $way]} 1 {eval proc ::thing::${self}::$way [lindex $args 0]} default {error "Usage: $self wayto ?name ?lambda??"} } ::set args } proc which {self name} { # way to know where a property or way came from #::set path [concat $self [::set ::thing::${self}::is-a]] foreach i [::set ::thing::${self}::is-a] { if [llength [info command ::thing::${i}::$name]] { return $i } if [info exists ::thing::${i}::$name] { return $i } } error "no $name for $self known" } } proc lremove {_list what} { upvar $_list list set where [lsearch -exact $list $what] set list [lreplace $list $where $where] ;# no harm when where=-1 } #----------------------------------------------- now testing... proc test {} { set test { thing new human legs 2 mortal 1 human new philosopher philosopher new Socrates hair white Socrates mortal Socrates legs Socrates set legs Socrates set legs 3 Socrates legs Socrates unset legs Socrates legs Socrates set beard long Socrates set human wayto sing {{- text} {subst $text,$text,lala.}} Socrates sing Kalimera Socrates wayto sing {{- text} {subst $text-haha}} Socrates sing Kalimera [thing new Plato] wayto sing [Socrates wayto sing] Plato sing Kalispera [human new Joe] sing hey Socrates } set n 0 foreach i [split $test \n] { puts -nonewline [incr n]$i=> puts [uplevel $i] } puts OK } time test
# On my P200/W95 box at home, the test suite took 490..600 msec.
Richard - pretty nifty -K6-2/475,W98 - 110msec so 11/27/2000 -went back and unloaded the system - time dropped to 50-60msec
pep - There might be an error in the ::thing::thing:new proc, 'set' should be '::set'
if [llength $self] { ::set t [concat $name [::set ::thing::${self}::is-a]] } else { ::set t $name }
Otherwise it would be refering to the ::thing::thing::set proc. I hope I didn't misunderstood something... By the way, thank you for this wonderful prototype based OO system!
For a comparison with Itcl, see Toasters and things
See Chaining things for a modified version that allows method chaining (among other slight changes).