Hacker News new | past | comments | ask | show | jobs | submit login
I can't believe I'm praising Tcl (2008) (yosefk.com)
119 points by panic on Sept 5, 2020 | hide | past | favorite | 58 comments

While I like Tcl, which I have used to write substantial portions of a shipping product, the author does make a good point about the difficulty of creating a language that is simultaneously a good programming language and a good scripting language: I love IPython dearly, but I've never seriously considered using it as a bash replacement (nor bash as a substitute for anything but the simplest Python scripts).

As an aside, I have to praise one particular, easily overlooked aspect of Tcl: its comm facility is, from a (non-Tcl) client implementation perspective, the easiest "foreign function interface"[1] I've ever worked with by far, due almost entirely to the simplicity and consistency of Tcl syntax.

[1] Technically, comm was designed as a remote execution facility, but it works just as well locally to run Tcl code from within a non-Tcl program without the hassle of embedding an interpreter.

Tcl is an aw some configuration language. Delete all the commands to make it non-Turing complete. Throw in infix assignment for ergonomics and you have a minimal configuration language that can be enhanced by adding back more commands as needed.

I think I get this. Would you have any code examples laying around?

MacPorts' Portfile[1], maybe? Portfile is being evaluated entirely within interp[2] with only few functions exposed inside the environment

[1]: https://github.com/macports/macports-ports/blob/master/lang/...

[2]: https://www.tcl.tk/man/tcl8.4/TclCmd/interp.htm

Very decent, that file from the Macports repo looks very clean and sane. Thank you.

Aw some?

> difficulty of creating a language that is simultaneously a good programming language and a good scripting language

I bridged that gap recently with GoLang. Below [1] is an example of how I now use GoLang instead of Bash for my scripting needs. The development cycle is essentially just 2 steps, like scripting, 1) Edit file(s) 2) Run the script (./my.go).

This gives the same feeling of writing scripts, but with all the guarantees of GoLang and benefits of an IDE (VSCode). The trick is to have the first line of the file be a script (for the process loader to execute) as well as a comment (so that GoLang infrastructure ignores that line.

[1]: https://gist.github.com/gurjeet/3121b2e373e61873c5a6e96d72b6... (Gist version at the time of submission: v3)

That is one interesting shebang. Nicely done. Can you explain the error handling stuff?

There are functions in log.go that emit message to stdout (thinking about it, I need to send warnings and above to stderr).

The function err(), in addition to sending a message to stdout, also causes an exit of the process. But it does so only if ExitOnError is turned on; which is on by default, and can be turned off by setting environment variable EXIT_ON_ERROR=true.

I have converted that Gist into a standalone Git repo, and hope to update it to add more improvements (like the stderr change, above) to showcase how to leverage GoLang for scripting.

[1]: https://git.sr.ht/~gurjeet/GoLang_Scripting_Example

Expect! I learned Tcl just for Expect. It’s one of those tools that takes you from “I have no idea how to do this” to “this is trivial” in just an afternoon.

Expect is such a useful tool, I am surprised that similar functionality is not found in most scripting languages.

I use the Perl Expect.pm module (https://metacpan.org/pod/release/RGIERSIG/Expect-1.15/Expect...) since I create and maintain a lot of Perl scripts. It combines the utility of expect with the power of Perl regexes.

There is [1] a pure python package for expect. It provides essentially the same interface and is incredibly useful.

[1] https://pexpect.readthedocs.io/en/stable/

I haven't used Expect in twenty-five years, I suppose, but it was really useful for some tasks I had then.

I recently dug up some old Tcl bindings in Go I wrote a few years ago for my employer and used them for a new tool. It really does shine when it’s used correctly. I like how easy it is to build a shell-like environment for the whole application. In a lot of ways it feels like the POSIX shell environment done right (and, yes, a few things — e.g. pipes — aren’t quite as good).

Although our tool itself is strictly in-house, when we get the time to give them a quality-control once-over we would like to open-source the bindings. Tcl and Go make a pretty good pair!

I would definitely reach for Tcl the next time I need to add a scripting language to a static-language project.

> (ever noticed how the space bar is much easier to hit than a comma, you enlightened dynamic language devotee? Size does matter).

This author makes so many good points about key ergonomics with languages. I’d love to see more research into this, and the results being incorporated into languages.

Hehe: http://www.cs.stir.ac.uk/~kjt/techreps/pdf/TR141.pdf

A paper that tries to compute Halstead language level for Haskell. It was so high they had to change constants in the computation of Halstead volume (page 6 in the paper above). Basically, many spaces in Haskell program are "apply" operator and one have to count these spaces to even attempt to be compatible with the theory.

I also have to say that I like Tcl very much but for different reason: values in Tcl are immutable and passing a dictionary or list to a procedure will not change that dictionary in unpredictable way (will not "pull the rug").

I once ranked languages by their support for drunken programming (diminished higher thinking ability - how much thinking programming language really takes from you) and Haskell and Tcl came out first and second - both have immutable values and Haskell also helps with the type system. Other contenders like C, C++, C#, Python and some other languages were much worse to use when one is drunk. Mainly due to the need to remember effects on the arguments passed to a function and/or method, but also for infamous "null".

C# had third place - some type safety and garbage collection help a lot (and compiler accepts more code than Java). Everything else just brings you more headache when you try to decipher and make work what you wrote being drunk.

When I was in college I once wrote Java code while drunk late into the night. I reviewed it the next morning (afternoon, really). The good news was that the code worked exactly as expected and produced correct results. The bad news was that it was completely incomprehensible. As in my sober self could not construct a mental model of how the code worked.

Then again, I also sometimes write code in my sleep if I left a problem unsolved during the workday and then I implement that solution when I wake up. Does anyone else also do this?

One time at university I was up late working on a math assignment, but couldn't figure out one of the problems. I fell asleep and had a dream in which I came up with the solution. I woke up briefly, wrote down the answer, then fell asleep again.

When I got the assignment back from the TA I had gotten everything correct, except for that problem, which he had marked up with red question marks and exclamation points. I looked at what I had written and found it to be incomprehensible nonsense...

This is my experience coding drunk as well. I also really hated the experience. As someone who enjoys getting lost and getting drunk and programming I thought I would love doing both together, it turns out for me drinking just maximizes the magical transient errors when coding caused by false assumptions which are always the hardest to debug.

These magical transient errors are exactly why one should choose language with immutable values. You have old value and you compute new one (with sharing if possible). You can continue to use old value or throw it away.

The mutation operation can be seen as resource management optimization, premature (and thusly evil) more often than not.

Did it make sense next time you got drunk? :P

I had to update some code that a more talented coder than I, wrote while stoned. It was an adventure.

Hah. I didn’t try, but that’s a good question.

Did you also consider Clojure? I suspect it would have made position 1 or 2 on your list depending on the problem to be solved.

Tcl IS the Clojure, but for C and it also has Tk, which is purely awesome (canvas elements have tags and event bindings).

I did these experiments when Clojure was not all that hip, circa 2008-2010.

We are vastly different programmers if you are being hindered by your ability to input.

If I could write code that was perfect and easy to read - but had to use nothing but my toes, I’d be investing in some fancy slippers and in a second.

I'm not sure if i understand you correctly, but if you think you aren't affected by where the relevant keys are, I would like to challenge you to get a German keyboard layout and write some program in C, Java or another curly braces language.

Recently I've discovered that TCL is a very pleasant language for shell scripting. The command-based syntax and the "everything is a string" design are a perfect fit for that problem domain.

Next time that you have a shell script that is too complicated for bash but not complicated enough for Python, give TCL a try!

Any examples of this to share?

I think Tcl would suffer from the lack of syntax for pipelines, for example.

One of these days I'm going to write a blog post about it...

The built in "exec" command supports pipelines and file redirections with the usual syntax so you can do

    exec foo < bar | baz
and it works similarly to a bash command substitution

    $(exec foo < bar | baz)
The main catch is that if you have an argument that is "|" or "<" then TCL interprets it as part of a pipeline, even if it came from a variable. However, the flip side us that you don't need to worry about arguments with spaces or asterisks. There is no word splitting or glob expansion so it is OK to leave everything unquoted.

    set r "<"
    set p "|"
    exec foo $r bar $p baz
There are also a couple things that are slightly different from bash. One is that exec raises an exception if the command returns a nonzero exit code. If that is not what you want then you will need to use the TCL equivalent of try-catch. I wrote a small helper function for this common use case.

Another thing is that exec returns a string with the output of the command, similarly to $() in bash. If you want to output to stdout then you need to work around that.

Everything-is-a-string is a terrible design. Also I don't think there is much space in between Bash and Python. I generally recommend ditching Bash for Python as soon as you have any flow control.

Everything being a string is not that bad in a world of shell pipelines, where all the commands arguments and results are strings anyway. And if you want you can also use data structures like lists and dictionaries. It is a bit idiosyncratic to support the everything is a string metaphor but under the hood they are still implemented as actual lists or dictionaries.

The main thing I dislike about migrating my bash scripts to python is that the syntax is more verbose. subproccess.call can be a bit of a mothfull and you "need" "to" "quote" "everything", "like“ "this". If the only fancy thing your script is doing is control flow then Tcl might be a good fit.

The quoting thing is definitely a benefit. Bash's lack of quoting usually leads to bugs (god help you if you have a space in your path).

I agree subprocess.call is quite verbose, but I find you have to do it less in Python scripts anyway because you can things properly with libraries rather than hackily calling out to other programs (e.g. curl).

I work in a very polyglot environment, and doing "everything properly with libraries" would mean duplicating code from language 1 to language 2 and possibly language 3 and 4 over and over again for zero benefit. Far from being "hacky" to call out to other programs, let's write the program only once, and have easy ways to glue to results of different programs together regardless of what language they might have been written in.

I am just learning about tcl today and am seriously considering replacing bash with it. Lack of quoting is looking very elegantly done in tcl, because of the way it handles {} and...it's not a scope, it's a single string argument. You can manipulate the string argument as a string, and you ultimately still know exactly whether you have space separated arguments or a single string because the grouping is explicit.

Tcl does not do automatic expansion on glob patterns unless explicitly requested with `glob`. Where bash might end up dynamically changing a space separated argument into two arguments when it gets passed around, tcl will give you a type failure for the equivalent scenario because the arguments do not fit the command given.

The ease of metaprogramming feats is really feeling incredibly LISPy, but without all the verbosity that turns me off from LISP...and with easy access to any commands in the host shell. That means LISPy control over a polyglot language context.


    puts "Hello world!"
    # Hello world!

    proc say {word msg} {puts "$word $msg!"}
    say Hello world
    # Hello world!

    set greeting "Hello"
    proc say$greeting {msg} {
        say [uplevel {set greeting}] $msg
    sayHello world
    # Hello world!

    set pyScript {
        print "Python"
    sayHello [exec python << $pyScript]
    # Hello Python!

    set nodeScript {
    sayHello [exec node << $nodeScript]
    # Hello Node!
I know this doesn't look this special, and basically just looks like doing normal bash stuff. But-

- there's no special handling you need to do with the scripts that need an outside interpreter,

- super easy to build these interactively within your tclsh, you can do macro-like actions on any of this very easily,

- you can send functions "from whatever language" around as arguments (and augment them) totally painlessly and not need to worry about syntax issues outside of the normal language context. You're either "inside" a UTF-8 encoded {} or you are not (in which case you are in the tcl context).

I agree. Python shines when you can use real libraries such as "requests" and if you are able to use a proper string/regex manipulation instead of being forced to live with sed/cut/awk. However, sometimes I really just want to invoke a bunch of shell commands, including pipelines. In those cases I gravitate towards Tcl or Fish.

Wait till he discovers Lisp. Tcl's flexibility combined with actual types (and not stringly typed). That's how I got seriously into Lisp: reading about the "Tcl Wars" and then trying out Guile.

That said, Tcl/Tk is still the fastest way I've seen to go from zero to GUI. Not even Visual Basic beats it in terms of quickness and simplicity.

Rebol is the only thing that really blows Tk out of the water from a GUI perspective. It's really a masterpiece of engineering. It's all based off of blocks and is really nice to use.


Also Powershell fully exposes WINFORMs, but it's not as streamlined as Tk, although seems to be more modern with more features.

For GUIs, you can have the best of the two worlds by using LTk (https://github.com/herth/ltk)

And cl-icebox (https://github.com/VitoVan/cl-icebox) is a build system, that can compile your Ltk apps into standalone executables for easy distribution.

I had seen this on hacker news before but forgotten the name! Thanks for reposting it!

Racket is pretty close to tcl in terms of ease of creating a GUI. I think tcl has a slight edge though.

TCL/tk has some rough edges.

But compared to most GUI toolkits especially anything cross platform, it’s really nice!

Hey... what's wrong with Tcl? Whenever I have a shell script and want to tack on a "GUI", Tcl/Tk is the most time-economical one for me. I am happy that it is still around, and that it works on most platforms (of course not mobiles... but I "wish").

Granted (on Android at least.) https://www.androwish.org/home/home

Oh man, that's awesome. You need to install by downloading a package.

If any maintainers are reading: putting it in the F-Droid store would improve visibility / discoverability.

Do you androwish?


This is great!!! I will try this out, and port some of my small apps on to my mobile.

"Lovecraftian toolchain" made me almost poop myself when I first read this article.

I've been learning TCL for fun lately and I both adore it and am frustrated by it.

> I have a bug, I'm frigging anxious, I gotta GO GO GO as fast as I can to find out what it is already, and you think now is the time to type parens, commas and quotation marks?! Fuck you! By which I mean to say, short code is important, short commands are a must.

Hahaha! This is excellent, made me crack a big IRL smile.

See also: https://news.ycombinator.com/item?id=7069642

(Nice discussion on homoiconicity)

The ease of creating and using child interpreters is a really neat thing about TCL.

What I like about it (at least for my usage) is that you compose functions like they were command line tool. That's some unique ergonomics right there (regardless of its cons).

Is Tcl still a COMPLETELY interpreted language?

I heard that they added a JIT compiler somewhat recently. But I'm not sure if there's any kind of "true" compiler (or at least linter?), that can find your basic errors prior to runtime.

I used to dabble a lot with Tcl back in the 1990's and early 2000's. I eventually moved into Lisp to scratch that side-project itch, mostly just because I wanted to find more mistakes at build-time than runtime.

Tcl switched to a bytecode compiler with version 8.0 about 20 years ago, which delivered significant speedups in execution.

Yes, but I thought that was a JIT compiler. For all practical purposes, still an interpreter model from the user perspective.

Does it allow for ahead of time compilation to bytecode? With build-time reporting of errors in the source code?

If you mean the wikipedia definition of compiling source code to machine code at runtime, then no. The Tcl interpreter contains a bytecode virtual machine, more along the Java model.

Source code is converted to bytecode on the procedure level, not the program level, so there's no program-wide checks for consistency. It's done at runtime.

There are some officially unsupported tools built in to the interpreter that let you dump bytecode of compiled procedures and reload it. You'd have to roll your own dumping and loading scripts if you wanted to deliver an entire program using those tools.

There is an AOT Tcl-to LLVM IR compiler in development.


A few years ago I wrote Tcl occasionally, macros for computer-aided engineering software.

As a linter, I used nagelfar (http://nagelfar.sourceforge.net/). I had it set-up in vim with Syntastic. From what I remember it worked OK, definitely helped compared to no linting.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact