(2024-10-07) Omnia mea mecum porto (feat. Tcl/Tk)
-------------------------------------------------
Two posts ago, I ranted about the lack of sane and decent desktop GUI
solutions. Now, I must emphasize that whatever I said there only applies to
compiled programming languages. The domain of desktop GUI scripting hasn't
been vacant for all these years and hasn't been only limited to JS, VBscript
(remember that shit?) or some obscure languages from the past like Rexx.
Popular interpreted programming languages like Python and Perl have been
having their own bindings to all possible C/C++ GUI frameworks in existence,
and some languages like Rebol and Red even have self-contained GUI
facilities. There is, however, one particular language that's simple enough
to learn it and start rolling functional GUIs in a matter of minutes,
lightweight and cross-platform enough to be sure your GUIs will run
anywhere, mature enough to deter any hype riders trying to parasite on it,
and dynamic enough to ward off "static typing is everything"-type snobs.
That's why, in the light of a recent new major version release of this
language and its accompanying graphical toolkit, I'd like to talk about
Tcl/Tk today.

What's interesting is that despite both of the names being acronyms (Tool
Command Language and Tool Kit respectively), they are just written with only
the first letter capitalized. Even without Tk, the Tcl language itself is
interesting on its own: it's built upon the same principles TRAC and REBOL
were built (even naming TRAC as one of its predecessors on its wiki), but
it's simpler to grasp than both of them. The language grammar itself is
defined with the famous Dodekalogue ([1]), sometimes combined into Octalogue
(the example is given on the same wiki page), but I have built an even
simpler understanding of what's going on there, and this can be formulated
in just several sentences.

In Tcl, everything is a string. A string containing a whitespace-separated
sequence of other strings is a list. Unless specified as a literal (within
double quotes or curly braces), the following three sentences apply to any
string. Any list is interpreted as a command. A script is a newline- or
semicolon-separated sequence of commands. Any string can be enclosed in the
square brackets which causes it to be interpreted as a Tcl script and the
result substituted in place of the string. If the string starts with $, then
it gets replaced with the value of the variable whose name is in the string
(not counting the $). {*} makes each item in a list an individual argument
of the current command.

That's it, that's the entirety of Tcl language rules. Of course, the
Dodekalogue also contains definitions of valid characters and backslash
notations and whatnot, but the basic understanding can be as concise as the
paragraph above. Everything else in the language builds upon the notions of
strings, lists, list expansion, variable and script substitutions. All
built-in commands like proc, if, for, foreach, list, dict, array etc have
nothing special about them and are just commands operating on lists and
strings. Even the comment operator # is just a command, this is why a space
is mandatory after it (unless it's a shebang) or a semicolon is mandatory
before it if you append it to an existing line of code. All this makes Tcl
even closer to Lisp than one would realize just because of how different
they seemingly look, although the LISt Processing nature makes Tcl use the
same prefix notation for all things in the world, even the mathematical
expressions need the "expr" command if you need to use anything infix. By
the way, conditional commands call "expr" implicitly, this is why you can
use infix expressions in the conditions as well.

From the functionality perspective, Tcl is quite a "batteries included"
language even not counting Tk, although the choice of builtin commands and
packages may seem quite strange. All this because it has a rather unique
distribution system and plenty of various implementations, sometimes not
very compatible with each other. Once you have Tcllib ([2]) up and running
though, I think you're pretty much set ([3]) for 95% of real-life non-GUI
development scenarios. The GUI part, as you might have guessed, is covered
by Tk and the Wish (WIndowed SHell) components of the Tcl/Tk distribution.
And there also is a Starpack system which, although quite outdated (the most
recent sdx kit file is from 2011), still works pretty well for packing any
Tcl application into a single binary file. Just make sure to include Metakit
(mk4tcl), Tcllib and TLS packages into your tclkit binary when building it
with whatever way you prefer. That's the absolute viable minimum. And, of
course, don't also forget to include Tk there if you're targeting GUI
development. On top of that, even the stock Tcl interpreter, tclsh, also
runs great interactivly as a REPL. Well, it becomes much more usable as a
REPL provided that you run it via rlwrap, the same way we did back in the
ed-related post.

Now, let's briefly talk about what Tk can offer us in terms of GUI. While the
Tk widget command syntax is not fully declarative, it's as close as it can
get to it with zero boilerplate and without sacrificing the flexibility. To
me, who spends a lot of time with command-line parameters, this syntax is
much more readable than XML or even HTML, let alone Go/Fyne, C/GTK, C++/Qt
or other "traditional" GUI toolkits, the only minor inconvenience being
widget creation and placement consisting of two separate commands. That,
however, takes place because Tk has three completely unrelated modes of
widget placement: absolute ("place" command), stacking ("pack" command) and
grid-like ("grid" command). In real life, you'll mostly find yourself
combining at least two of these three modes, so you definitely need some
flexibility when using them, so I don't really regret the creation and
placement not stuffed into a single line of code. As for the widgets
themselves, you can find everything you really need for desktop GUI
programming out there, and the most recent version, Tk 9, even added support
for host OS printing, notifications and systray. Now you really have no
excuse to move elsewhere.

The most underappreciated Tk feature though, in my opinion, is bidirectional
data binding out of the box. I reckon this only is available when you use it
directly with Tcl and not through other language bindings like Tkinter. The
thing is, you can assign a global variable to any widget that can change its
parameters, and whenever the user modifies the widget contents (enters text,
clicks checkboxes, moves sliders etc), the variable automagically changes,
and vice versa. You don't need to subscribe to any change events or signals,
or call the value retrieval method or whatever, it just happens by itself.
This way, the actual widget value always stays in sync with what the user
sees.

One last thing: contrary to popular beliefs about the bland and obsolete UI
style, Tk is themable. In fact, it is now recommended to create all widgets
from the ttk (themed Tk) namespace. The non-themed Tk engine is only there
for backward compatibility with previous versions. Besides several
preinstalled themes ("default", "alt", "clam" and "classic"), you actually
have plenty of options with ttk. You can even try to match the host OS style
if you want to. For instance, for normal OSes, there is a dynamic theme
called "gtkTtk" ([4]) that you can separately build on your system and have
it pull whatever GTK theme you're currently using to apply the same style to
your Tk applications. For other OSes, there are "aqua", "winnative",
"winxpnative" and "vista" themes to match corresponding native widget
styles. In your own code, if you're trying to target as many people as
possible but don't want to include your own theme, you can even write
something like this in the catch blocks:

# iterate over available platform-dependent themes, apply "clam" if none found
ttk::style theme use clam
catch {ttk::style theme use aqua}
catch {ttk::style theme use winnative}
catch {ttk::style theme use vista}
catch {ttk::style theme use gtkTtk}

Well, I guess this covers the most basic stuff about this incredible
scripting language. I think it deserves more attention than it gets,
especially in the modern world of total nonsense and bloatware,
**especially** when it comes to interpreted programming languages and
graphical frameworks. Some people might argue it's not "enterprise-grade"
enough despite its history in computing is already quite long. For me
though, Tcl/Tk still is something really viable for day-to-day desktop
scripting. I can imagine creating a bunch of small utilities for personal
use (both online and offline) that could easily fit on a floppy (let alone a
thumbdrive or a microSD) and travel with me from system to system, from
environment to environment, not being affected by anything external at all
as long as the underlying Tcl/Tk version can run there. And with version 9,
it got even better, although I still will have to stick to 8.6 for some
time, at least until the 9 gets ported to the AndroWish/undroidwish stack
and some other places that new versions get ported to much later than the
upstream ones. Regardless, it still feels exciting to rediscover such a
language and its capabilities and see a new major version released after a
long time. Now, I think, desktop GUI development finally got a chance, after
all.

--- Luxferre ---

[1]: https://wiki.tcl-lang.org/page/Dodekalogue
[2]: https://www.tcl.tk/software/tcllib/
[3]: https://core.tcl-lang.org/tcllib/doc/trunk/embedded/md/toc.md
[4]: https://github.com/Geballin/gtkTtk