proc thingy name {proc $name args "namespace eval $name \$args"}(MS: see at bottom for a faster version using interp alias)suchenwi: This is very poor, no inheritance at all. It just sugars the namespace wrapping, which in turn allows instance "variables" and "methods".
thingy foo foo set bar 1 ;#== set foo::bar 1 foo proc grill x {subst $x!} ;#== proc foo::grill x {subst $x!} foo grill sausages ;#== foo::grill sausagesmiguel: Not bad at all! It does have the nice property that properties and methods *are* regular tcl vars and procs.suchenwi: And, as I just tried, it can be nested:
foo thingy baz foo baz set id 42 ;#== set foo::baz::id 42 foo baz proc wow x {subst x?}rmax: seems to be related to this one: Namespace resolution on unknown commandsuchenwi: rmax: Exactly. At that time, Larry Smith proposed that feature for the Tcl core, but like often, you can do it yourself - in a one-liner, in this case...rmax: suchenwi: I prefer to write "proc foo baz wow {x} {subst x?}" It is more readable IMHO.suchenwi: Of course you can do it like this. But for the parser, {x} is "x" is x. YM foo baz proc wow {x} {subst $x?}rmax: Yes, I know that x == {x} . My point was, that the semantic of "proc foo baz wow {} {...}" is more obvious than "foo baz proc wow {} {...}"suchenwi: Oh. Yes, I just reread that Wiki page. The advantage of the above plaything is minimal effort, and orthogonality e.g. with "set", where "set foo bar" could not be taken as retrieving the value of foo::bar...suchenwi: Methods could be dynamically shared like in UFO; variables via upvar.miguel: On closer looks, it is (almost) perfect; good thinking, RS! Inheritance will pose some challenges though:
- (a) inheritable procs will need to have the object pointer passed to them ("self/this/@")
- (a') inheritable procs should refer to everything by global name
- (b) inheriting a proc means copying it (as with waytos ...); what with compiled commands?
proc thingy name { namespace eval $name proc $name args "namespace inscope $name \$args" }miguel: It only works for single commands, but uses the faster eval on pure lists (I think, haven't timed it ...)
rmax: This adds copying of prototype objects:
proc new {name} { uplevel 1 thingy $name set current [uplevel 1 namespace current] set parent [uplevel 1 namespace parent] foreach variable [info vars ${current}::*] { set vars $variable if {[array exists $variable]} { set vars [list] foreach n [array names $variable] { lappend vars ${variable}($n) } } foreach var $vars { namespace eval ${parent}::$name \ [list set [lindex [split $var ::] end] [set $var]] } } foreach cmd [info procs ${current}::*] { set arglist [list] foreach arg [info args $cmd] { if {[info default $cmd $arg def]} { set arg [list $arg $def] } lappend arglist $arg } namespace eval ${parent}::${name} \ [list proc [lindex [split $cmd ::] end] \ $arglist [info body $cmd]] } }rmax: I think, I just found a bug in your oneliner from this morning: it places all thingies (even the nested ones) in the global namespace.rmax: here is one, that nests correctly:
proc thingy name { proc [uplevel 1 namespace current]::$name args \ "namespace eval $name \$args" }suchenwi: Ah, I see. YM if the caller is in some namespace himself.rmax: OK, the namespaces are nested as expected but the procs are all in the global namespace.rmax: ... so you can't have "foo proc bar" and "baz proc bar" at the same time.suchenwi: I can! The original one-liner that started this discussion creates ::foo::grill and ::bar::grill which are independent.rmax:
proc thingy name {proc $name args "namespace eval $name \$args"} thingy foo foo thingy foo1 puts [info commands foo*] => foo foo1suchenwi: Yes, I see.rmax: namespace children :: foo* => foo foo1
BOOP is another super-simple all-Tcl OOP framework, in the same spirit as Thingy.
MS An almost-equivalent of the original one-liner is
proc thingy name {interp alias {} $name {} namespace eval $name}To correct the nesting behaviour as per rmax's comments, do instead
proc thingy name { set name [uplevel 1 namespace current]::$name interp alias {} $name {} namespace eval $name }The difference is that calling $name with no arguments will create the namespace in the original version, but cause an error in this one. This one is also faster in current tcl:
[mig@mini mig]$ tclsh % info patch 8.4.1 % proc thingy name {proc $name args "namespace eval $name \$args"} % proc thingy2 name {interp alias {} $name {} namespace eval $name} % thingy a % thingy2 b b % time {a set x 1} 10000 36 microseconds per iteration % time {b set x 1} 10000 23 microseconds per iterationPAK I had trouble getting the interp alias one liner to work within my small object system because namespace eval flattens the list.
% proc thingy2 name {interp alias {} $name {} namespace eval $name} % thingy2 a a % a puts "hello world" can not find channel named "hello"Instead I needed 'namespace inscope', which turned it into a two liner because inscope does not create the namespace:
% proc thingy3 name { namespace eval $name {} interp alias {} $name {} namespace inscope $name } % thingy3 b % b puts "hello world" hello world
PAK Adding a bit of sugar for creating object ids and deleting objects, thingy does make for a very compact object system:
namespace eval obj { variable id 0 proc thingy {} { variable id set obj [namespace current]::obj[incr id] namespace eval $obj {} interp alias {} $obj {} namespace inscope $obj $obj proc delete { args } { rename [namespace current] {} namespace delete [namespace current] } return $obj } }Since every object carries around its own methods, instead of having a class construct you can use a simple proc to populate your object:
proc Bat {{ball foo}} { set obj [obj::thingy] $obj set v $ball $obj proc hit.ball ball { variable v; set v $ball } $obj proc ball {} { variable v; return $v } return $obj } % set b [Bat] ::obj::obj1 % puts [$b ball] foo % $b hit.ball bar bar % puts [$b ball] bar % $b deleteThis is not the most efficient in terms of creation time or memory, but dispatch is fast and the implementation is simple. You even get inheritance if instead of calling obj::thingy your class proc calls the constructor for some other class. Chaining destructors is a little bit messy, but you can do it if you rename delete to delete#myclass and call delete#myclass at the end of myclass's delete proc. The same trick will work for any other method that needs to call to its parent method. Instance variables can be traced in widgets using e.g., -textvariable [set b]::v.It would be nice to clean up the object when there are no more references to it, but short of an array style implementation based on trace unset, or maybe resorting to a C implementation, this doesn't seem possible.
Thingy OO with classes adds class functionality to thingy in a style similar to XOTcl.
OO libraries another one-liner like approach but very different