Tcl is the best scripting language for the real but still productive hacker (some of the fattest Schemes are not far), as long as you don’t need a specific library not easily available;
-
Extremely small - whole syntax/grammar is described in 12 rules in a single man page (Tcl(n)) of 150 lines and there’s no reserved keyword.
-
Homoiconic through strings (like almost every language with eval) but most importantly, through “list-like” strings.
-
Official man pages! No web garbage like cppreference nor lackluster “minimalist” stuff like pydoc (compare
pydoc print
withman n puts
, for example). -
Kind of “unfashionable” language meaning basically no poz.
-
At least two implementations with jimtcl being a quite thorough embedded one.
-
One of the simplest if not the simplest interaction with C, letting you write C plugins very easily (with critcl and swig to help).
-
Comparable in speed to cpython, a bit slower than Perl 5 and Lua. Has a WIP/dead LLVM compiler (tclquadcode) for a big speedup in the far and uncertain future.
-
Cool type system that is superficially “everything is a string” (like sh) but in reality “everything is a tagged union with on-the-fly conversion when needed and a unique string representation”. Allows for some very cool things like editing dictionaries as lists or lists as strings and transparent serialization (
puts $mydict $chan
<=>set mydict [read $chan]
). -
Talking about types, multiprecision arithmetic is transparently handled, allowing you to do
expr {42 ** 1000}
if you just want to. -
Very powerful introspection through
info
(mainly). Allows for stuff like getting the name/body/arglist of a procedure, get all the registered procedures, know if a variable exist, get information on the stack frames and their content, etc…Together with
trace
, you can write an internal debugger in few lines. See https://wiki.tcl-lang.org/page/Full+program+trace+onwards for an example. -
Procedure arguments are passed by pointer with a copy-on-write system: don’t modify the argument and you don’t get any memory copy. To you, it just looks like regular passing by value.
-
On the subject of simplicity, no need for an actual garbage collector, reference counting is enough because you cannot make circular references.
-
Modifying the procedure arguments is done via
upvar
: in Tcl, a variable reference is just a name (string) with a relative stack frame number, quite elegant considering the language’s concepts. -
If you use at least the builtin extensions (thread, http, tdbc, tcltest, msgcat) and the very basic tcllib/tclX/tclUdp/tklib packages, you’re almost set for life. Personally, I also recomment the very convenient tclreadline, tdom, pipethread, tablelist and tclcurl.
Some more here: https://core.tcl-lang.org/jenglish/gutter/
-
Channels is one of the cleanest I/O implementation I’ve ever used with some cool features:
- Transformations allowing filters like deflate/zlib/gzip or TLS to be put on
a channel (see
transchan
for the API). - Reflected aka virtual channels, to make your own channels. Basically like glibc/BSD’s unportable fopencookie/funopen or CL’s gray streams.
- Centralize a lot of ioctl/fcntl shit and even more (like defining the EOF
char) in
chan blocked/configure/pending
. - Integration with the event loop via
chan event/postevent
allows for a nice callback oriented approach to sockets and pipes. - Other third-party channel types include pty (expect), random, memory or fifo (memchan).
- Transformations allowing filters like deflate/zlib/gzip or TLS to be put on
a channel (see
-
Builtin event loop (see
after
,vwait
,socket -server
andchan event
) for powerful and seamless concurrency/command scheduling. -
An elegant thread extension consisting of an interpreter per thread and no raw access to other thread’s memory. Comes with both simple (
thread
) and performant (tsv
) synchronization/communication facilities. -
Finally a sane, light and portable (even more with Tile) GUI toolkit: Tk.
-
One of the fastest Unicode aware regex implementations, written by Henry Spencer himself. Has its own greater-than-POSIX-ERE syntax called ARE, not as complete as PCRE (lacking lookbehind constraints, most importantly), but still great for an hybrid NFA/DFA engine.
-
uplevel
(eval a script in a different stack frame) andtailcall
(replace the current procedure with another command) let you augment the language by implementing control structures and keywords yourself. Inferior to CL’s synergy between unhygienic macros, “naked AST” style homoiconicity, symbols as first-class objects, gensym and quasi-quoting, but still quite powerful. -
Safe interpreters let you do very fun things like config files in Tcl with limited access to the machine and master interpreter.
-
Recent versions (>= 8.5) really embraced FP with:
- Real lambdas (but not closures, these have to be emulated) through apply.
- Purer hash maps (dict) than ugly sticking-out-like-a-sore-thumb arrays.
- Lisp style prefix arithmetic (allowing for
* 3 [+ 1 2]
instead ofexpr {3 * (1 + 2)}
) including sane behaviour for more than two (reduce) or zero (neutral element) arguments. - Builtin map/filter (lmap) with 8.6. See https://wiki.tcl-lang.org/page/Functional+Programming for more.
-
Multiple more-or-less powerful OO systems (now based on the builtin TclOO): [incr Tcl] for C++ style OO, XoTcl for a take on CLOS or Snit for something Tk oriented.
-
All of the above with the same advantage of CL: it does not enforce nor preach a particular way of programming, unlike the ML family that comes with the “everything is immutable” ball and chain that often gets in the way instead of helping (because, big surprise, modifying data is often an essential part of efficient and intuitive algorithms/programs while recursing only makes sense when manipulating recursive data types; which arrays - arguably THE modern big-cache-and-SIMD-CPU friendly structure - aren’t).
-
Biggest inconvenient are the near-death state of the language/community (practical consequences: no LSP/SLIME equivalent, bugs accumulating, lack of maintainted libraries) and the design warts/“features” (e.g. list <=> string relation preventing possible differentiation between
x
,{x}
and{{x}}
). As someone who knows a bit of CL, the language itself is less powerful (e.g. macro vs uplevel, lambda vs apply) but the standard library quality is miles ahead for a lot of common OS interfacing tasks (in CL, you need at least UIOP, osicat, usocket, cl-ppcre to compare; and Ltk for Tk).
Basically, a mix of Lisp and Scheme that somehow managed to end up very good and getting even better with time.
I could continue all day, but you should just try it. Some more talk about it:
- https://wiki.tcl-lang.org/page/What+is+Tcl
- https://wiki.tcl-lang.org/page/Tcl+Articles
- https://colin-macleod.blogspot.com/2020/10/why-im-tcl-ish.html
- https://yosefk.com/blog/i-cant-believe-im-praising-tcl.html
- http://antirez.com/articoli/tclmisunderstood.html
Where to begin:
man n Tcl
(orman 3tcl Tcl
on some distros like Debian)- https://wiki.tcl-lang.org/page/TCL+for+beginners
- https://wiki.tcl-lang.org/page/Tcl+Tutorial+Lesson+0
- https://wiki.tcl-lang.org/ for everything, this is where the Tcl community lives
- Rosetta Code for examples/exercises