JMN 2003-08-02 The Ghosts of VB haunt this TCLer.
After a year or so of using TCL nearly every day - I've suddenly struck what feels like a mid-TCL-life crisis... I've actually caught myself pining for Visual Basic. Yes VB, that much-scoffed-at MicroSoft language, can actually be a very powerful creative environment; allowing you to focus on the problem at hand and to easily toss around complex structures much larger than can fit in a mere programmers head.
It may surprise some to learn that for many years it's been possible to write multithreaded VB applications with asynchronous calls and quite decent concurrency & performance for a serverside system supporting at least a couple of hundred LAN users. I get the impression sometimes that outsiders view it as some sort of glorified GUI builder. Such astoundingly hypocritical criticism coming from those using operating systems and scripting languages that have only in the past year (2003) or so (e.g FreeBSD, perl, TCL) even begun to catch up, in the area of threading, to the level that advanced VB users have come to assume as a given.
My reasons for 'leaving' VB were more political (open-source vs proprietary) and strategic (desire for multiplatform) than any feeling that the language itself may at some point put bounds on my programming creativity.
There's plenty that can be said both on the shortcomings and advantages of VB, TCl or any language for that matter - but my current mini-crisis of confidence in TCL is related fairly specifically to what might usually be called 'Object Oriented Programming'.
TCL suffers/enjoys, depending on your view or mood, from an array of different options for programming in a more or less 'Object Oriented' fashion. There are packages such as Snit, XOTcl, Stooop, ClassyTcl, itcl and various interesting object-like constructions in the wiki such as Chaining things.
I'll admit I've merely perused IncrTcl, and that of all the OO packages available, I've only actually written code using Snit & XOtcl. I've also played around with namespaces and my own perversion of Richard Suchenwirth's On things to get a feel for the basic mechanisms TCL alone offers in this regard.
While that is hardly a comprehensive investigation into the OOP options available for TCL - it's so far been disappointing and led me to peer back at my old VB code with more than a hint of wistfulness.
It's VB's power to organize your objects into a deep & wide custom hierarchy of object references that make it such a joy to work with.
Whilst OO enthusiasts may espouse the virtues of such complex OO concepts as inheritance (worse yet, multiple inheritance) & prototypes & mixins, the very straightforward aggregation and referencing supplied by VB, along with some IDE sugar in the form of dropdown statement completion, are IMHO the key to great deal of programmer freedom and productivity.
The freedom in particular comes from the ability to create object structures that echo your view of a portion of the problem, and then allow some of these structures to 'fade from the mind's working memory' safe in the knowledge that the IDE's dropdown hints, and the simple dot-separated object reference syntax, will help lead your mind back into the structure as necessary.
Though the VB OO world feels clean and generally simple to the programmer, it's not completely without pitfalls. One still has to worry about such things as forgotten circular references undesirably prolonging the lifetime of a set of linked objects for example; but by and large it's simple enough that the use of objects feels like a natural way to express your solutions and you don't find your thoughts on the problem at hand being interrupted too much by the technicalities of implementing it.
Now the drop-down 'method & object selection' of the IDE is nice sugar, but perhaps not entirely necessary; my feeling is that it's the unified delimiting syntax for accessing the object-hierarchy & methods that really keeps the object-system out of the way of my problem-solving thoughts.
Below is a rough example of the sort of things you can do with VB that I'm having trouble finding an analogous approach for in TCL. This code is incomplete and perhaps not syntactically 100% as I've had myself firmly ensconced in the TCL code for the past year or so, and my VB may be rusty. (Perhaps also my memories of what I didn't like about VB have faded..so my opinions here may be tainted a little by a case of those rose-coloured history-goggles looking back at the supposed 'good ol days' ;)
'Enterprise is a variable referring to an instance of some kind of spacecraft class set Enterprise = New USSSpaceCraft Enterprise.nameOfCaptain = Kremen set Enterprise.warpdrive = new InfiniteImprobabilityDrive ' etc etc ' set various other properties.. some time elapses adventures are had.. set SpacePort.dock1.SpaceShip = Enterprise set SpacePort.lastDocked = SpacePort.dock1.SpaceShip set marvin = SpacePort.systems.maintenance.bots(1337) marvin.addToShipInspectionTasks(SpacePort.lastDocked) marvin.shiptasks("Enterprise").statusReport 'marvin will of course want to tell us something bad, like the ship's main 'airlock probably won't want to open, and anyway he doesn't feel like telling 'us how to get a reference to the airlock object. 'Luckily, we don't have to use marvin's reference to the ship, we can use any 'of various other paths to the ship object, so a little bit of hacking around 'on the station's VB-driven commandline will get the door open. '(no as far as I know MS hasn't really released a VB shell/commandline ' - but this spacestation has one anyway!) marvin.shiptasks("Enterprise").ship.airlock("main").open("sesame") set Enterprise = CollectionOfAllShipsThatMightDockHereOneDay("Enterprise") Enterprise.airlock("main").open("sesame") SpacePort.dock1.SpaceShip.airlock("main").open("sesame") SpacePort.lastDocked.airlock("main").open("sesame","please") 'clunk
Now my frustration with the TCL OO world is that it appears to be difficult if not impossible to create such structures without winding up in a morass of command brackets or a rigid set of namespace trees that don't lend themselves to being pruned and rearranged and in any event, don't allow arguments to be passed to an item somewhere in the middle of the chain.
I can't even envisage what the TCL code would look like for a neat solution.. and I'm hoping that TCL's syntax itself is not the showstopper that will forever make such manipulations clumsy.
Let's try translating this line:
marvin.shiptasks("Enterprise").ship.airlock("main").open("sesame")
into some hypothetical TCL OO system that may or may not currently exist. Would it be something like this?
[[[marvin shiptasks "Enterprise"] ship] airlock "main"] open "sesame"
or this?
marvin::shiptasks::Enterprise::ship::airlock::main open "sesame"
We could of course break it down into lots of steps and hold variables for each object - but this would be a case of the mechanics of solving the problem interfering with the creative flow - and verbosity isn't normally a crime I'd expect of TCL.
In some ways the closest thing I've seen to a neat way of accessing a deep, malleable object hierarchy in TCL is the manipulation of XML using Tdom and xpath. See in particular Brian Theado's Natively accessing XML
This is however a case of using a syntax external to TCL (xpath) to perform the object-structure navigation - and it seems like a good solution for XML. Perhaps I'll have to go outside of TCL syntax and resort to some sort of string 'accessor' to get this kind of effect in a TCL OO situation.
I don't think I'm in real danger of migrating back to VB - as I've found a lot to love about TCL - but I'd really like to stop looking backwards with a sense of loss.
Please, if someone can demonstrate an idiom, using any of the myriad OO extensions available for TCL; for the sort of object referencing/aggregation & manipulation that VBers have enjoyed for so long, I'd be grateful.
Maybe you would feel more comfortable with a language like Python, which makes what you want to do very easy.
jmn Yes, I think maybe I would be comfortable with Python, but I'm also in general comfortable with TCL . This page owes a little of its inspiration to pages such as Things holding Tcl back , Tcl Advocacy & About Tcl and popularity - It's my current commitment to Tcl and its use to solve my problems that leads me to examine how TCL appears to an ex-VB user. Of course I can't speak for ex-VBers in general, and in fact I'd be quite interested to hear from other TCLers who once used VB for their opinions on the migration process, and what they feel they've lost and gained. I love TCL - but at this stage I simply couldn't look a VB user in the eye and say that a year or so on into their TCL usage - they'll be equally or more productive. Considering TCL's inherent simplicity - this came as a bit of a shock to me. I suspect TCL may be missing out on the opportunity to cultivate a lot of VB users looking to make a change... maybe they are all going to Python.
Joe Mistachkin Or you could do what I do and use TclBridge to combine the best of both the Tcl/Tk and Visual Basic/COM worlds.
jmn If it was the COM side of things that I was particularly missing, this might have been a useful approach for some situation. While I think the ability to easily call your object models across process boundaries is a great plus - at this stage I'm concerned more with manipulation of local data structures, and for this I think a mixture of TCL & VB is not a hybrid that will help at all in simplifying things.
SLB are you looking for something like this:
proc traverse {object args} { foreach arg $args { set object [eval {$object} $arg] } return $object } # VB marvin.shiptasks("Enterprise").ship.airlock("main").open("sesame") becomes Tcl: traverse $marvin {shiptasks Enterprise} ship {airlock main} {open sesame}
jmn This looks promising. I'll have to play around with it and see what can be done with it.
Well, tk widgets are objects. They are called with arguments. So if one were to create a similar environment for ships, one might have a series of commands to create objects ( usSpacecraft , tasks, airlock , ... ) and then call something like
usSpacecraft .marvin -tasks [ship Enterprise -ship [airlock .main -open sesame]]
jmn This doesn't look good to me at all. While it may work for GUI systems where over time one learns the particular options and idioms for certain widgets, I think this syntax would be somewhat intrusive and unintuitive for dynamic object structures.
Your example syntax from above works ok with XOTcl (and nearly any reasonable Tcl OO system), you can even drop the quotes:
[[[marvin shiptasks Enterprise] ship] airlock main] open sesame | | | | | | | | Obj instproc argument | | | | | ------------------------- | | | | | | | | | | | Obj instproc | | | | ------------------------ | | | | | | | | | Obj instproc arg | | ------------------------- | | | | | Obj instproc arg
For more comfort one could override unknown or use a convenience proc like traverse from above, but if the [ ... ] are ok, this simply works. And with XOTcl's sophisticated system you can change and rearrange the structure as you want, even on a per Object basis.
jmn Despite my comments above re inheritance & mixins, I do actually find XOTcl quite nice in this regard. It does seem to give a lot of flexibility. I'll have a go at putting a 'traverse' wrapper over it in some way, but to date I've found XOTcl to be a bit more mental work than I'd like. I don't expect everything to be simple, but the simple things should be simple to build as it were..
I think you miss the point - the original poster (OP) doesn't WANT to use the [...] notation...
jmn That's right. I wouldn't want people to think I'm in any way railing against TCL syntax, in fact for most things I find it just fine. For navigating objects however, I'd like to be able to start a command line not knowing how deep in the structure I'm about to go, and move left to right without the need to regress and fill in the left brackets. It may seem a trivial requirement - but it's my argument that it's more intrusive than one might think, and when you're trying to quickly throw down a bunch of lines, having to go back and count brackets is a drain on the stream-of-thought that can result in ideas falling out of my overtaxed little mind.
GPS Here's a quick example I wrote of doing something like this with SDynObject. BTW SDynObject.tcl is 94 lines of Tcl code.
source ./SDynObject.tcl #package require SDynObject works if you install it proc USSSpaceCraft obj { $obj model unknown #The user could have just done $obj model blah, but this is an example... sd.new.method set.model for $obj takes model { $self model $model } sd.new.method get.model for $obj takes {} { return [$self model] } return $obj } proc InfiniteImprobabilityDrive obj { $obj warp 0 $obj maxWarp 0 sd.new.method goto.warp.n for $obj takes n { if {$n > [$self maxWarp]} { puts "warp $n is beyond this engine's capabilities (ask Scotty)" return } if {[$self warp] < $n} { puts "Increasing to warp $n..." } else { puts "Decreasing to warp $n..." } $self warp $n } return $obj } proc main {} { set Enterprise [sd.new.object] USSSpaceCraft $Enterprise $Enterprise->set.model "NC-95343" $Enterprise nameOfCaptain Kremin $Enterprise warpDrive [InfiniteImprobabilityDrive [sd.new.object]] [$Enterprise warpDrive] model 350 [$Enterprise warpDrive] maxWarp 10 puts "Ship is [$Enterprise->get.model]" [$Enterprise warpDrive]->goto.warp.n 1 #We're going fast. [$Enterprise warpDrive]->goto.warp.n 6 #I'm feeling queezy. Slow down young whippersnapper... [$Enterprise warpDrive]->goto.warp.n 2 #Klingons are attacking... [$Enterprise warpDrive]->goto.warp.n 12 } main
Example output:
$ tclsh84s.exe Enterprise.tcl Ship is NC-95343 Increasing to warp 1... Increasing to warp 6... Decreasing to warp 2... warp 12 is beyond this engine's capabilities (ask Scotty)
Oh, and one last thing. If you want to refer to the warpDrive without using brackets you simply do:
set warpDrive [$Enterprise warpDrive]
and then:
$warpDrive->goto.warp.n 5
jmn This syntax too looks reasonable - but it's not clear to me yet how it'll help for deeper traversals into an object reference hierarchy. Setting variables for everything on the left prior to the final method is sometimes ok - but is other times as disruptive as bracket-counting.
One might think my apparent aversion to bracket-counting would mean that functional languages leave me cold - but in fact I'm also quite a fan of Erlang
Thanks for all the responses so far - it's given me a bit more to investigate and think about.
Jacob Levy August 02, 2003: If you dont mind typing one extra word, you can have your VB cake and stay in Tcl too :) I modified SLB's code slightly to make it even more VB'ish:
proc VB {cascade} { set args [split $cascade .] set object [lindex $args 0] set args [lrange $args 1 end] foreach arg $args { set object [eval {$object} [split $arg ()]] } return $object } # VB marvin.shiptasks("Enterprise").ship.airlock("main").open("sesame") becomes Tcl: VB $marvin.shiptasks(Enterprise).ship.airlock(main).open(sesame)
Note that I didnt test this actually :)
jmn 2003-08-04 This approach is what I meant above by a string 'accessor'. Looks like it might be good for a Playing VB page :)
SLB The use of split is fine for simple examples, but could cause problems if you extend things a little. For example:
set ship "U.S.S. Enterprise" VB $marvin.shiptasks($ship).ship.airlock(main).open(sesame)
The 'split' is going to break up $ship. Seems to me, you'd need some quite fancy parsing to make this approach robust enough to handle arbitrary strings as arguments.
This page spawned a small package to give me some of what I'm after: objectwalker