Am 26. September war es endlich so weit: Ein neues Major-Release der Programmiersprache Tcl (ausgesprochen „Tickle“, wie das englische Wort für „kitzeln“) ist erschienen. Für die Sprache, die sonst eher auf evolutionäre Entwicklung, Rückwärtskompatibilität und Library-Unterstützung setzt, ist das ein entscheidender Schritt.
Tcl besticht seit seinen frühen Tagen mit innovativer Funktionalität, die es noch gar nicht so lange in anderen Programmiersprachen gibt, wie etwa eventbasierter Ein- und Ausgabe und Variablen-Traces. Die Innovation hört auch nicht auf – in den letzten Minor-Releases sind etwa „Reflected Channels“, also frei scriptbare Ein- und Ausgabekanäle, Tailcalls, Coroutinen mit voll bidirektionaler Kommunikation sowie unbegrenzte Ganzzahlen als Standard hinzugekommen – alles Konstrukte, die elegante und vor allem einfache Lösungen ermöglichen, die in anderen Programmiersprachen viel wuchtiger ausfallen würden.
Hier ein kleines Code-Beispiel, um zu zeigen, was in Tcl geht:
namespace import tcl::mathop::*
proc printSquare { varName index operation } {
upvar $varName value
puts [* $value $value]
}
proc main {} {
set tracedVar 1
trace add variable tracedVar write printSquare
while 1 {
incr tracedVar
after 1000
}
}
main
In diesem Code sieht man, dass es möglich ist, mit Traces live benachrichtigt zu werden, wenn sich eine Variable ändert. Das ist ein Alleinstellungsmerkmal von Tcl, da das nicht wie in anderen Sprachen, wie JavaScript, auf Objekte beschränkt ist, sondern mit allen Werten funktioniert. Außerdem kann man sehen, dass mathematische Operatoren einfach als Befehle eingebunden werden können. Die eckigen Klammern [ ]
sind die Syntax für einen verschachtelten Befehlsaufruf. Wir rufen also den puts
-Befehl mit dem Resultat der Multiplikation auf, und zwar immer, wenn tracedVar
einen neuen Wert bekommt.
Neuheiten in Tcl 9
Nun legt Tcl 9 einen darauf. Das mit Spannung erwartete Release, das im September endlich das Licht der Welt erblickt hat, wagt es nun, viele Innovationen einzuführen, für die unter der Haube und in der C-API jedoch einiges umgestellt werden musste. Darunter befinden sich diverse Performance-Verbesserungen, eine Komplettüberholung der Unicode-Verarbeitung und abstrakte Listen, mit denen die C-API so aufgebohrt wurde, dass sie Daten in und aus Tcl-Skripten streamen kann, über die die Skripte einfach iterieren können. Auch ein Highlight: Unterstützung für Single-File-Deployment, bisher ein eigenes Paket, ist nun integriert. Es folgt in nächster Zeit auch noch Unterstützung für Unix-Domain-Sockets, um lokale Interprozesskommunikation noch effizienter zu machen.
Merkmale von Tcl
Die Sprache weist einen hohen Grad an Abstraktion auf, und Boilerplate lässt sich fast immer vermeiden. Dadurch wird nicht nur Code eingespart, sondern auch viele Fehlerquellen reduziert – ganz nach dem Motto „Define errors out of existence“, geprägt vom Vater der Sprache, John Ousterhout. Eine Besonderheit, die in diesem Zusammenhang nicht unerwähnt bleiben sollte, ist, dass Tcl nicht den milliardenschweren Fehler der Null-Referenzen eingegangen ist.
Möglich ist das unter anderem dadurch, dass Tcl relativ konsequent Kopier-Semantik umsetzt. Das heißt, auch Listen (in anderen Sprachen als Arrays bekannt) und Dictionaries verhalten sich, als würden sie kopiert, wenn man sie als Parameter übergibt. Es ist ungefähr so, als würde man in JavaScript immer den ...
-Operator in einem neuen Array oder Objekt benutzen. Unter der Haube läuft das aber optimierter ab, als in JavaScript, und man muss sich nie mit Referenz-Fehlern und diversen Fernwirkungen herumschlagen. Gerade für die funktionale Programmierung ist das ein geniales Feature, wie wir finden. Einen Spread-„Operator“ gab es in Tcl übrigens schon vor JavaScript, man schreibt ihn {*}
.
Tcl mag auf den ersten Blick etwas unscheinbar oder aus der Zeit gefallen erscheinen, aber dieser Eindruck ist nicht berechtigt. Tcl-Code und -Unterstützung befinden sich in der Git- sowie SQLite-Distribution, in den allermeisten FPGA- und VDHL-Tools sowie in vielen Telefonanlagen. Weitere Anwendungen sind Chatbots, Flug-Tracking und Kaffeemaschinen. Schlussendlich die Krönung: Jeder, der die Standard-Python-Distribution verwendet, hat auch Tcl installiert. Denn Python enthält eine komplette Version von Tcl und Tk. Das unterstreicht nachdrücklich, wie kompakt und leichtgewichtig Tcl geblieben ist.
Die Plattformunterstützung ist breit: Alle modernen unixoiden Betriebssysteme, darunter auch macOS, sowie Windows ab 7 werden unterstützt, daneben gibt es noch AndroWish für Android, mit dem auch App Development möglich ist. Die Sprache ist gleichzeitig eine C-Bibliothek und kann somit auch überall eingebettet werden.
Wir sind weiterhin überzeugt von Tcl und nutzen diese Sprache aktiv, vor allem für alles, was mit Automatisierung zu tun hat.
Der eigene Weg von Tcl – und was man daraus lernen kann
Eines der Geheimnisse von Tcl ist der Fokus auf Explizitheit. Vieles, was in anderen Sprachen implizit ist und subtile Bugs verursacht, wird expliziert und durch eigene Befehle abgebildet. Das Setzen von Variablen, das Indizieren einer Liste oder das Erzeugen eines Dictionaries etwa sind explizite Schritte mit eigenen Befehlen. Das läuft vermeintlich dem Gedanken von Abstraktion und Boilerplate-Vermeidung zuwider, entpuppt sich aber als robustes Sicherheitsfeature. Die Sprache ist so gut strukturiert, dass man explizites Type-Checking wie in TypeScript oder neueren Python-Versionen niemals vermisst.
Typisierung in Tcl
Um Typen muss man sich in Tcl praktisch keine Sorgen machen, denn Werte haben (zumindest konzeptionell) gar keine Typen in Tcl. Stattdessen erwarten Befehle ihre Argumente in einem gewissen Format, etwa Integer, Entier (einer unbegrenzten Ganzzahl), Dictionary, Liste, Datum oder JSON. So implementiert Tcl eine Art strukturelle Typisierung.
Tcl ist eine homoikonische Sprache, ähnlich wie Lisp oder Forth. Das bedeutet, dass Tcl-Code auch ein spezieller Datentyp ist. Das macht Tcl extrem geeignet für Metaprogrammierung, domänenspezifische Sprachen und transformierendes Parsen.
Ãœbersichtlichkeit und Pragmatismus
Zuletzt eine etwas subjektive Bewertung, die aber auch von vielen Tcl-Neulingen geteilt wird: Tcl-Code ist sehr übersichtlich und gut lesbar. Zum einen liegt das an der Explizitheit der Sprache und daran, dass die Basis-Syntax extrem simpel ist. Zum anderen aber auch an sinnvollen Abstraktionen und „little languages“, die in anderen Sprachen oft nur durch umfangreiche Libraries (wie date-fns
oder lodash
in JavaScript) bereitgestellt werden.
Tcl ist bis ins kleinste Detail durchdacht. So akzeptiert etwa der Befehl lmap
, das Pendant zu map in vielen anderen Sprachen, anstandslos auch break
– und continue
-Befehle. Dadurch ersetzt lmap gleich auf einen Schlag filter
, find
, some
und viele Anwendungen von reduce
. Und jeder, der break
– und continue
kennt, versteht auch den Tcl-Code sofort:
set remotesRaw [exec -ignorestderr git remote -v]
set remotes [lmap remoteRawLine [split $remotesRaw \n] {
split $remoteRawLine
}]
set pushOrigin [lmap remote $remotes {
lassign $remote name uri typeInParens
if {
$name eq {origin} &&
$typeInParens eq {(push)}
} {
set uri
} else {
continue
}
}]
Dieser Tcl-Code zum Beispiel findet unter den Git-Remotes in einem Repository genau die Origin-Remote und speichert sie in einer Variable. Hier der äquivalente, idiomatische Python-Code:
import subprocess
remote_output = subprocess.check_output(['git', 'remote', '-v'], stderr=subprocess.DEVNULL).decode('utf-8')
remotes = [line.split() for line in remote_output.splitlines()]
push_origin = next(remote[1] for remote in remotes
if remote[0] == 'origin' and remote[2] == '(push)')
Hier erkennt man einen deutlichen Unterschied, denn Tcl erlaubt es durch seine flexiblen Syntaxen und Ausführungsmodelle, den Code viel selbsterklärender zu gestalten, als das in Sprachen wie Python möglich ist. Im Gegensatz zu Python, das oft auf unterschiedliche Kurz- und Langformen der Syntax setzt, hat Tcl einheitliche Befehle, die sich nach Lego-Prinzip kombinieren lassen. Streng gelintetes JavaScript oder TypeScript hat daher übrigens auch eine etwas Tcl-artige Struktur.
Weitere Highlights
Tcl verfügt über ein integriertes Test-Framework, das unserer Meinung nach eines der besten auf dem Markt ist. Es basiert auf dem Prinzip „Ein Assert pro Test“ und integriert diesen direkt in die Syntax. Jeder Test hat einen menschenlesbaren Titel ohne unnötigen CamelCase
. Außerdem verfügt das Framework über ein Matcher-System, um die Asserts flexibler zu machen, und über ein Constraint-System, mit dem man Test nur unter bestimmten Bedingungen (z.B. Plattformen oder CI-Umgebungen) ausführen kann. Gerade letzteres ist ungemein praktisch und fehlt in den meisten Frameworks. Hier ein Beispieltest, direkt aus dem Tcl-Core:
test string-4.13.$noComp {string first, start index} -body {
run {string first 牦 abc牦x end-2}
} -result 3
In der heutigen Welt, wo Sicherheitsupdates mehrmals täglich ausgespielt werden, ist natürlich auch das Thema DoS omnipräsent. Bei JavaScript und Python sind ReDoS-Attacken häufig ein Problem. In Tcl nicht, denn die Regex-Engine verwendet einen Algorithmus, der prinzipbedingt Strings in unter-exponentieller Zeit matcht. Dabei sind die regulären Ausdrücke genauso mächtig wie auch in anderen Sprachen. PostgreSQL verwendet die Engine von Tcl und profitiert daher ebenso von diesen Sicherheitsvorteilen.
Der Tcl-Core-Code ist von äußerst hoher Qualität und, wie für Programmiersprachen mittlerweile üblich, unter einer permissiven Lizenz verfügbar. Er zeigt gut, wie man in C wart- und erweiterbare Systeme entwirft. Jeder C-Programmierer sollte sich den Tcl-Source einmal zu Gemüte führen, um wertvolle Best Practices daraus zu übernehmen.
Nachteile von Tcl
Kein Artikel bei AVANT ICONIC kommt ohne eine ausgewogene Betrachtung von Trade-Offs aus. Nicht anders ist es hier. Auch wenn ich zugegebenermaßen begeistert von der Sprache bin, muss ich einige Einschränkungen und Probleme einräumen.
Eingeschränkte Tool-Unterstützung
Für Tcl gibt es einen soliden Linter namens Nagelfar sowie ein Formatierungs-Tool. Diese sind aber bei weitem nicht so opinionated wie ein ordentlich eingerichtetes eslint
oder prettier
. Das halten wir für einen leichten Nachteil, denn so treten beim gemeinsamen Arbeiten häufiger Git-Konflikte auf.
Auch gibt es derzeit noch keine nutzbare LSP-Implementierung, sodass die Code-Completion in IDEs wie Visual Studio Code vergleichsweise eingeschränkt ist.
Fehlende Bibliotheken für Web-Development
Tcl hat den Trend zu Web-Frameworks nicht mitgemacht und kann daher für viele Web-Themen keine Bibliotheken vorweisen. So gibt es etwa keine OAuth- oder JWT-Bibliothek, außer im Framework OpenACS, das aber eher ein komplettes CMS ist. Für die Entwicklung von APIs und Services gibt es daher zwei Optionen: Die nötige Funktionalität selbst entwickeln und auf ein leichtgewichtiges Framework wie Woof oder Wapp für das Request-Handling zu setzen, oder gleich das große, monolithische OpenACS zu nutzen.
Fehlender Package-Manager
Ein Bereich, wo Tcl anderen Sprachen hinterherhinkt, ist das Package-Management. Das war nicht immer so, das teacup
-Tool erfüllte vor etlichen Jahren diese Funktion, es wird jedoch nicht mehr gepflegt. Nun ist mit BAWT eine gangbare Alternative verfügbar, wo auch viele Tcl-9-Packages momentan eingepflegt werden. Den Status als Standard, wie NPM oder PyPi, hat BAWT aber momentan noch nicht.
Da wir Tcl-Applikationen oft in Containern ausführen, werden wir in einem zukünftigen Post noch berichten, wie sich BAWT in den Docker-Workflow einfügen lässt.
Conclusion
Das neue Release beweist, dass sich in der Welt von Tcl/Tk immer noch viel bewegt. Die Sprache ist weiterhin ein Vorbild für pragmatische Ansätze, erfolgreiche Kombinationen von Paradigmen und Mut zur Nische. Es hat die Tcl-Community klar motiviert, weiter am Ökosystem zu feilen und seine Schwächen auszumerzen. Wenn es so weitergeht, hat Tcl noch ein langes Leben vor sich.