In defense of Tcl

3 comments:

Anonymous
said...

It's an excellent overview. The article easily dispenses with the common misconceptions about Tcl and presents the features of the language in a non-irritating way. For example, not many people (or even Tcl users) are really aware of the equivalence of Tcl "uplevel" and Lisp macros. Many of my colleagues at a Tcl-using company, even those previously acquainted with Lisp, never really "got" uplevel. Despite all that, the article is still goes too easy on Tcl.

For example, although Tcl lists are in essence strings (with some caching optimizations in Tcl 8), this is still far from having a real printer a la Lisp or Python. For example, while you can send a list over the wire, you can't send an array or a proc. Even worse, you can't send an array to another proc, so you have to resort to upvar, which is common enough to be an "idiom" in its own right.

Here is another example. More complex data structures are handled in Tcl by providing "handles" to the user. This is all good and similar to other scripting languages, except that here the "handles" are merely short strings that act as keys to the procs that know how to recognize them. This means that: a) there is effectively no garbage collection (you have to remember to call a proc that will deallocate the resources behind the handle), and b) you cannot, from Tcl, query an object's type.

Which brings us to the next problem: there are no types whatsoever. And not just in the sense of there being no declarations (which is also true for other scripting languages, as well as a number of non-scripting ones). The objects themselves don't carry type information accessible from Tcl! You cannot find the type of anything, either in the sense of Python's type(foo) or even as in Perl's ref($bar). So if your proc receives an argument that is a list, you cannot find whether its elements are sublists or strings -- they all look the same. If you want to do that kind of thing, tough luck! You have protocol to add explicit type tags to each tree element. It's worse than coding in C, really.

For the same reason, the number conversion arithmetic is deeply broken. Tcl handles the conversion of octal 0ooo and hex 0xhhh at string-to-number conversion time, not at source-code-constant reading time (see above, Tcl has no clue how to distinguish strings from numbers, or indeed strings from anything else). This might sound like not much of a problem, but it is, because if you, say, parse a date, and the user types "08" for month, you get an obscure error, and you won't notice it for 01, 02, etc., only for 08 and 09. Note that this doesn't happen in Perl or PHP (other popular languages with transparent runtime on-demand string-to-number conversion); there expression "012"+1 evals to 13, not to 11.

The comment parser can bite you in unexpected ways. For example, it took me hours to trace why this code is a syntax error:

Turns out the comment is implemented as just another command (or behaves as if it were implemented that way), so braces must match regardless of comments. The worst thing is that this is not just some idiosyncrasy of the current implementation, it's a direct consequence of the brain-dead Tcl design and it can't be fixed, ever. To understand why it can't be fixed, just remember that Tcl's code reader does not and cannot distinguish strings from code blocks. Therefore the command if {...} {...} might in some other case be db_foreach {...} {...}. In the case of db_foreach, the first argument is a list, and the second argument is a code block, but the reader (or the runtime engine) don't know that -- they only see db_foreach called with two string arguments. Removing the comments from that string, or missing the ending brace based on what could or could not be a comment is clearly wrong.

Then, the hailed "uplevel" -- while it allows you the effective power of macros, those "macros" must be reevaluated on each entry to the proc. A good macro system evaluates the macro only once, typically at compile-time or, for interpreters, at function definition time. The use of uplevel makes such optimization, and indeed any kind of compilation, downright impossible, because the compiler has no way of distinguishing between my_puts {hello bob} and my_repeat {puts bob}. In both cases the stuff between the braces are just strings.

I could go on with this. The baroque extensions to function calls, with keyword arguments being simulated with shell-like switches, inheriting all the quoting problems that shell has (an argument that begins with a "-" is suddenly a problem). The exception mechanism that requires non-trivial large boilerplate code to the the equivalent of "catch" or "unwind-protect", the result of which being that most uplevel macros out there don't properly handle non-local exits such as exceptions, "break", "continue", and "return". Of course, the boilerplate code cannot be abstracted in a function, it has to remain inline. Then there's the lack of GC, lack of bignums...

The intended purpose of Tcl, to provide a "glue language" for abstractions written in low-level languages, has been largely supplanted by languages such as Python, Perl, and Lua, and at least in my opinion those simply do the job a lot better.

To summarize, not all attacks on Tcl come from quarters that "misunderstood" the language. Some have studied it and abhor the shell-like syntax. Some have worked with the language and its extensions and decided them to be insufficient for their needs. Some have simply experienced better scripting languages. Most have moved away from Tcl and never looked back.

About uplevel and re-evaluation at every procedure call VS lisp macros, note that Tcl is powerful enough to have real macros written in Tcl itself. I wrote a macro system called Sugar for Tcl you can find at http://wiki.tcl.tk/sugar that works in a similar why to Lisp's, that's macros are expanded at procedure creation time. Even if it's an hack and developed in Tcl itself Sugar is powerful enough to perform tail call optimization automatically using some macro.