Clipped from the Tcl chatroom on Feb 5, 2001:
suchenwi: This weekend I was thinking about minimal OO and came to the following one-liner:
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 sausages
miguel: 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 command
suchenwi: 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:
miguel: As it is, you can send not only single commands but also longer scripts (commands separated by ';' and properly quoted). If you restrict yourself to single commands, the following variante should be faster:
miguel: Well (back to thingys): it is faster, but less elegant:
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 foo1
suchenwi: 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 iteration
PAK 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 delete
This 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
DDG - 2021-08-26 - Thingy in Action! The Readme of pandoc-tcl-filter at http://htmlpreview.github.io/?https://github.com/mittelmark/DGTcl/blob/master/pandoc-tcl-filter/Readme.html contains an example on how to create svg files using Thingy.
DDG - 2021-08-28 - I created a minimal package out of the Thingy SVG creator called tsvg where a separate Download available via gitdown: tsvg package . Documentation will follow ...
DDG - 2021-08-30 - Documentation for the tsvg package - the Thingy SVG writer - is now here: http://htmlpreview.github.io/?https://github.com/mittelmark/DGTcl/blob/master/pandoc-tcl-filter/lib/tsvg/tsvg.html
DDG - 2021-09-06 - Documentation for the tdot package - the Thingy DOT writer - is now here: http://htmlpreview.github.io/?https://github.com/mittelmark/DGTcl/blob/master/lib/tdot/tdot.html