Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Why Tcl? (gist.github.com)
181 points by elvis70 on June 20, 2023 | hide | past | favorite | 151 comments


I've managed to work on two TCL codebases in my career, one in 1992ish, and one in 2011ish. I was very surprised to be working on it the second time! I've said it before, but working on TCL is interesting, kinda fun, and ultimately a lesson in frustration once you get to a certain sized codebase. It really hammered home the need for static typing as your repo gets over 50k lines because it is so "type free". However, it is a cool thought experiment for it's premise, which is more-or-less, "what if everything is a string". It has a lot of benefits, and a lot of nasty side effects. I'm glad I spent time doing it, and I think the use case for which it was made ("Tool Control" roughly speaking) it has good ideas. TK was fun, as well, I miss it in some ways. But ultimately, I find TCL is a dead end for a programming language - the negatives accumulate too quickly.


You can have any type you want, as long as it's a string. :-) Don't forget about https://core.tcl-lang.org/expect/index. Comes in very handy sometimes.


Tcl may not be strongly typed, but it is "stringly typed". :)


after using expect and TCL heavily for years, I can firmly say they suck at scale.

Ruby https://github.com/ytti/oxidized/blob/master/lib/oxidized/mo... or TextFSM https://github.com/google/textfsm

are much better options if you need to do a /lot/ of parsing


Tcl hasn't been string based for over 20 years. It's time for this myth to die.


If it's a myth, the myth seems driven by the TCL team itself.

https://wiki.tcl-lang.org/page/everything+is+a+string

Yes, there is some detail about internal dual representation.


That wiki page has been around since before Tcl 8. Nobody says lisp is hampered because everything is an S-expr.


> That wiki page has been around since before Tcl 8

Maybe, but it's clearly been updated after Tcl 8. And while the page itself isn't calling it something that hampers tcl, it does cover that it's somewhat unusual, that is has implications, and so on.


Technically, all values are considered subtypes of strings, but the notion of types is quite different to those of many other languages. In particular, Tcl's types do not describe the memory storage model of their values. (They're implemented with 64-bit words and buffers and arrays and so on, but that's not what the value model describes.)

It works well as long as your goal isn't to totally eliminate boxing of values.


I never understand people's frustrations until I hear 50k loc base lol. That sounds hard in any language. I'd guess Tcl is pretty awesome if you keep things around 1000 loc or below (i.e. scripting).


> That sounds hard in any language.

Yes, it is hard in many languages, having experienced it first hand. Just wanted to point out my experience with Elm (I know) 200k loc, I can refactor the codebase fearlessly, knowing that once it compiles it will "mostly" just work.

I fondly remember porting Elm 0.18 to 0.19, which was a big change in the language itself and the libs, me and my co-worker working across time zones almost 24/7 for over a week to make the damn thing compile, it was like struggling in the darkness, unable to see "anything" in the browser for like 7 days and when it finally compiled.. it mostly just worked!

It's sad that to see Elm getting so much negative publicity, but I do enjoy working every day on it.


Last I checked Elm was getting negative publicity not because of the language itself (heard a lot of good things about that) but because of the leadership?


Correct. Elm's publicity issues stem from how closed off the leadership is. There's Evan (creator) and a handful of trusted others that have a huge amount of sway over how the language and, arguably more importantly, the core libraries develop. They also get special access to write libraries that rely on native JS code (something you can't do as of 0.19 even locally in your own projects).

Elm is a great language and I've written extensively[1] about how nice it is to use in production. But if you want to get involved in the community, it feels like there's not much to get involved in. The fact that even on the Elm Discourse[2], posts auto-lock after 10 days means that the forum is now pretty dead compared to what it used to be in 2018-2019.

[1] https://charukiewi.cz/posts/elm/

[2] https://discourse.elm-lang.org/


I have the same feeling with Swift. I did a refactor recently w/o really thinking about it, just fixing (compilation) errors as they happened, and it just worked on first try.


In something like C# or Java with a good ide I imagine it's not too bad.

Interpreted languages like Python and JavaScript do suffer when code bases reach a certain size.


Even adding half-baked, not compile/runtime checked types such as "Python's Type annotations", "Python's Type Stubs", "Javascript @type-infused-jsdocs" and "Javascript with Typescript Declaration Files" manages to make those modern IDEs a lot smarter for things like checking if you're doing really obvious bad things until you go really ham into metaprogramming.


I still work on a TCL codebase. I hate it. It makes heavy use of upvars, uplevels, and even a DSL written in TCL. It's so limiting. The interpreter only uses a single thread so better hope you don't need to do much computing with it. It's so hard to debug. Oh and be careful not to use brackets in your comments cos it interprets comments as if they're code...

The codebase is too big to rewrite but we're slowly untangling and hacking bits off to write in golang, but most of the business logic is in that DSL.


It's the exact same story with Python and Lisp. I love dynamically typed languages for fun little projects, but really don't like working with them on big must-not-fail type projects.


The author should rather be honest and say "I like TCL just because", instead of coming up with contrived examples to state alternatives are bad.

E.G, in the Counter snippet, why make some complicated code when Python is designed to make it simple?

    import sys, collections

    for p in sys.argv[1:]:
        try:
            with open(p) as f:
                for k, v in collections.Counter(f).items():
                    if v > 1:
                        print(v, "\t", k, end="")
        except Exception as e:
            print("dup error: ", e)
I don't buy the argument of simplicity here. Also, if you are going to just do "get", you don't need requests, the stdlib is fine.

And I don't get the speed argument either, given his script uses most_common(), that basically sorts the entire dict, which none of the other codes do. Yes Python is likely slower, but let's not push it.

Also the Go code is more complicated, but you can cross compile it and ship it as is. And it's fast.

I get it, TCL is cute, but making up arguments is not selling it.


Yeah—as a TCL fan, I agree. Comparing implementations of common coding tasks with other languages doesn’t highlight TCL’s strengths or, really, its purpose.

Personally, TCL fits a niche like QBASIC did. QBASIC was the shortest path from brain -> code drawing on a monitor. Literally 1 line to draw a shape, no initialization and not even an entry point function.

TCL similarly lets you just get on with what you’re doing when you need glue code, GUI’s, and DSL’s. It is easily (even trivially) able to do things that are just a pain in other languages, especially all at once:

- interop with native code (no limits on who calls who or in what order)

- define GUI’s that behave well without it being a huge pain to make anything non-trivial (lookin at you, UE5)

- create novel control flow that feels built-in

- implement an interactive GUI debugger with breakpoints & variable watch in ~200 LOC (saw this once, it’s amazing what you get “for free”)

- save or load a running program’s entire state, including code defined at runtime

- detour any function, allowing you to optimize or patch on the fly

- run from tiny, standalone executables so your users don’t have to install a ton of crap just to run your widget

There’s more but you get the idea. It’s been around for decades for a reason :)


That's actually a much better sale pitch than the article.


TXR Lisp:

  (each ((file *args*))
    (catch
      (dohash (k v [group-reduce (hash) identity (op succ @1)
                                 (file-get-lines file)
                                 0])
        (put-line `@v\t@k`))
      (error (e)
        (put-line e *stderr*))))


The author, while having a passing familiarity with it obviously, is clearly not a Python expert, so take all the Python stuff with a major grain of salt. Like they claim not to know how to get granular exceptions out of the requests modules:

>The requests here is a third-party package that required installation and a virtual environment to use. I don't know how to catch more granular exceptions in this example.

so I guess they even didn't RTFM:

https://requests.readthedocs.io/en/latest/api/#exceptions


The "take command-line input and print it out" in Python is even more succinctly written:

  import sys
  print(*sys.argv[1:])


The gist of those examples in TypeScript:

EXAMPLE 1: Echo command-line input

    console.log(process.argv);
EXAMPLE 2: Count duplicates in a file

    import { readFile } from "fs/promises";
    readFile("0.txt", "utf-8").then((text) => {
      const frequency: { [line: string]: number } = {};
      text
        .split("\n")
        .forEach((line) => (frequency[line] = frequency[line] + 1 || 1));
      Object.entries(frequency)
        .filter(([key, value]) => value > 1)
        .forEach(([key, value]) => console.log(value, key));
    });
EXAMPLE 3: GET request

    fetch(process.argv[2]).then(console.log).catch(console.error);
EXAMPLE 4: GET parallel requests

    process.argv.slice(2).map((url) => {
      const start = Date.now();
      fetch(url).then((res) =>
        console.log(
          `${(Date.now() - start) / 1000}s`,
          res.headers.get("Content-Length"),
          url
        )
      );
    });
Using the author's criteria, relative to Tcl, these implementations (to me) are:

1. Significantly shorter, just as expressive, and familiar to millions of (JS/TS) devs

2. Fast, owing to Node.js/V8's insane optimization level

3. Strongly typed

But you know what? I don't care much about terseness or speed when writing scripts. Honestly, the real reason I use TS for scripts is simple: comfort. I'm comfortable and productive with it. Everything else is just post-hoc rationalization. I'd wager it's the same story for many of these "Why <language/tech>?" posts.

It's perfectly fine to use something simply because you like it -- you're in good company. :)


I'm pretty sure your examples behave differently from the example programs.

For example, 'console.log' will pretty-print the data structure, rather than a single space separated line, the duplicate counter only reads from one file (with a hard-coded name) and will not correctly handle errors and move to the next file, and your parallel get doesn't seem to print one duration for all requests to complete.


Ergo the weasel word "gist" of the examples! :) Want variadic arguments? Wrap your code in `argv.slice(2).forEach(mySweetParser).catch()` or whatever. My point wasn't to scrupulously transpile Tcl to TS via HN dialog boxes. I don't hate myself that much. My point is just that the real work in those examples can easily be hogged out faster via other languages, so Tcl isn't an outstanding tool for brevity.


i frown on anything JS because it always end up requiring some sort of 'npm install' or, shivers, 'npm install -g'. it's just as bad a running a random 4gb java binary with extra steps.

js will only run on users machines or inside a vm without even kvm because we've seen plent ecosystem hijacks and exploits in the wild already. and you can only fool me MAX_INT.


Generally, when you see a package asking you to install it globally with `npm -g i`, you can install it locally with `npm i` and then run it with `npx`. Requesting global installation is either an expression of ego or the author is a Python refugee.


The above code requires no 3P dependencies. With a few notation tweaks, you can paste/pipe those examples right into a (~40 MB) `node` binary and get answers.

Where security is concerned, I can't say that Node.js has been more problematic than any other language I've used, but that's anecdotal and YMMV.


> 2. Fast, owing to Node.js/V8's insane optimization level

But you need to convert the TS to JS, and the TS compiler takes like 3 seconds to start on my laptop (orders of magnitude slower than any other compiler I know, although you can speed it up a bit if you disable type checking, but that, well, removes type checking).


You don't need a compiler. The vanilla `node` binary works fine with a few trivial syntactic changes.

It sucks that your compiler's slow! Check out `swc` if you prefer speedy compilation.


swc doesn't do type checking, so while useful in some cases it's not really a replacement for tsc.


Does node do static type checking?


Nope! `node` is just a binary that executes JS code (e.g. the above code, mod a few syntax choice tweaks). Since JS has no static types, `node` has no types to check.

Other languages can be compiled (perhaps "transpiled") to JS, some of which have static type mechanisms. One of those other languages, TypeScript, has static types. There are tools to compile TypeScript into JS: `swc`, `tsc`, etc. There are also tools to statically type check TypeScript (`deno`, `tsc`, etc).

Personally, I use `swc` for speedy TS compilation. I use my IDE's `tsc` language server for the separate job of highlighting type issues. But everyone has their preferred workflow!


> But you need to convert the TS to JS

Or use deno? Or bun?


I used to think POSIX shell scripts are the best. But ever since deno support npm packages directly, running `deno run url-to-scripts` seems really good now. You get readability, types, and library ecosystem.


        .split("\n")
It looks like this has the same issue as the Tcl code, where the empty string after a terminal "" is treated as a line. I think that is an error in the Tcl.

You'll also need a .catch(err => console.log("dup:", err));

And the console.log(value, key) needs a tab separator.

And a loop over the argv input files.

All minor tweaks which don't detract from your conclusion.


Rather than splitting, we can positively tokenize: recognize lines as items that match lexical tokens. They will include the terminating newlines that we need to trim away.

We need to be tolerant to files that are not terminated by a final newline.

This regex seems to do the trick (under the TXR Lisp tok function):

  1> (tok #/[^\n]*./ "")
  nil
  2> (tok #/[^\n]*./ "no newline at end of file")
  ("no newline at end of file")
  3> (tok #/[^\n]*./ "\nno-newline")
  ("\n" "no-newline")
  4> (tok #/[^\n]*./ "line\nno-newline")
  ("line\n" "no-newline")
  5> (tok #/[^\n]*./ "line\nline\n")
  ("line\n" "line\n")
  6> (tok #/[^\n]*./ "\n")
  ("\n")
  7> (tok #/[^\n]*./ "\n\n")
  ("\n" "\n")
  8> (tok #/[^\n]*./ "\n\n\n")
  ("\n" "\n" "\n")
A line is a (maximally long) sequence of zero or more non-newline characters, followed by a character.

LOL; I don't think I've ever used regex-driven tokenizing to recognize Unix-style lines in a text stream.


Or we can do like the Python code does and depend either on line iteration of the input file, or use str.splitlines(). ;)


All those tools conceal finite automata which have to do the equivalent of that regex pattern.


Yes, of course. Lines of text are classic regular grammars so anything parsing them is a DFA at heart.

Is there a take-home message I'm supposed to get from your comment?

My comment was meant to be a light-hearted observation that you're overthinking the problem, since your language of choice likely has this built-in.

Or a boast that Python has a built-in feature for what requires a custom TXL and/or TypeScript solution. ;)


Yes, line splitting is built-in. But this is more about split versus tokenize.

I have split and tokenize functions which have the same interface.

Split places the focus on identifying separators: the "negative space" between what we want to keep. When we use that for lines, it has problems with edge cases, like not giving us an empty list of pieces when the string being split is empty.


I find it difficult to be interested in this philosophical difference between the two when only difference is in how they treat the empty string.

That is, splitting with regex look-behind/look-ahead, like:

  (?<=\n)(?=.)
with a suitable definition of dot, also matches "negative space", and produces the same results as Python's str.splitlines(True), except for the empty string.

  >>> import re
  >>> p = re.compile(r"(?<=\n)(?=.)", re.DOTALL)
  >>> def check(s):
  ...   lines = p.split(s)
  ...   assert lines == s.splitlines(True), lines
  ...   return lines
  ...
  >>> check("no newline at end of file")
  ['no newline at end of file']
  >>> check("\nno-newline")
  ['\n', 'no-newline']
  >>> check("line\nno-newline")
  ['line\n', 'no-newline']
  >>> check("line\nline\n")
  ['line\n', 'line\n']
  >>> check("\n")
  ['\n']
  >>> check("\n\n")
  ['\n', '\n']
  >>> check("\n\n\n")
  ['\n', '\n', '\n']
  >>> check("")
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 3, in check
  AssertionError: ['']


Tcl is a little gem of programming lore, an ugly duckling story where the ducklings are LISPs instead.

One of the most mind-blowing things to me as a (perpetual) Tcl noob is that when you look at

    proc my_function {x} {
        puts $x
    }
You are lead to believe it is an ordinary C-inspired language with an odd syntax. It took me an embarrassingly long amount of time to realize that

    proc my_other_function "x" "puts \$x"
Is equivalent to the former. Other little oddities like `upvar` are beautiful once you understand them. It has some warts but Tcl has never had the luxury of under the scrutiny that languages like Python or Ruby have had. This is one of the reasons I would really like to see a "modern" version of Tcl more suitable for programming in the large---something that happened to Python 3 almost as an accident, imho.


> a "modern" version of Tcl more suitable for programming in the large

Would lua fit that definition? Between game engines, redis, openresty (nginx w/ lua) and others I feel like it fits a similar need (adding code to existing applications).


I've done a fair amount of programming in Lua in the past but it still feels different from Tcl. Back in 2010 I would probably compare Lua to (a better) JavaScript due to its prototype-based nature. JavaScript evolved in a different direction afterwards.


The language itself is very different, but Lua is a replacement for Tcl in the sense that it's a fairly simple and small language that can easily be embedded. i.e. it's about use cases rather than the language design.


I understand what you mean, but this is not the point I was trying to get across with my original comment. Maybe that's the reason why I haven't touched this subject. Although both languages can be easily embedded, the programming experience is still different, and there are still lots of "freestanding" systems in both languages, so the language design comparison still applies.


The command-argument based workflow is so easy to grok as a model that it is intensely loveable. Generating arguments at runtime without messing up your substitutions can trip people up occasionally, but the straightforward nature of how the compiler understands the function is relatively wonderful IMO.


Well, there are classes and objects in there now. They're designed for modelling pretty heavyweight entities like widgets instead of lightweight things like linked lists.

It was a fun OO system to write. It's much more dynamic than it appears to be at first glance; it's first glance looks much like many other languages object systems with a few slight oddities (no new operator, but rather classes have a new method).


  #!/usr/bin/env tclsh
  
  puts $argv
I kinda wanted to stop reading after this example. It feels dishonest to start with an example for which there is a builtin. Why didn't you show instead how to output arguments joined by a comma or some other separator instead of space. If I were to update the Go or Python examples to use another separator, it'd be just a 1-2 character change, whereas for Tcl I have no idea how that would look.


> Why didn't you show instead how to output arguments joined by a comma or some other separator instead of space.

The Tcl variant to output "comma" as separator (note -- this is not "CSV").

     puts [join $argv ,]
If you wanted "comma space" you'd do:

     puts [join $argv ", "]
If you actually wanted "CSV" then it would be (assuming tcllib is installed):

     package require csv
     puts [csv::join $argv]


Funnily neither version gives the correct result when you have arguments with spaces, e.g. for "-v 5" -d , the tcl one will print {-v 5} -d as a consequence of how tcl represents lists.


The author should have done:

     puts [join $argv]
to avoid that little issue. Doing "puts $argv" triggers Tcl's output of "lists" in a special format that allows the list to be parsed from the text again at a later time.


The author is following examples given in a book, however.


Oh, I missed that. I guess it's alright then. I thought they chose this particular example to "showcase" Tcl by comparison.


Most of the digital semiconductor design EDA / CAD tools from Cadence and Synopsys use Tcl as the built in scripting language. I have to work with thousands of lines of Tcl code whether I want to or not.

I learned Tcl / Tk back in 1995 because I wanted to write GUI programs and Hello World in Motif was about 2 pages of boiler plate code while in Tcl / Tk it was about 2 lines.

Then I started in the chip design world and Tcl took over so it was good that I already knew it. I was teaching the older engineers Tcl so it helped me gain their respect.


Although I never used Tcl myself, I've worked in/with teams that need to interface with these EDA tools, and most of the folks there are not SW programmers, but electronics folks who need to write Tcl to get their job done.

Their code sucked like crazy. Usually in such teams you are not evaluated by the quality of code.

Ever since then, any time I'm job hunting and I see a listing for a SW programmer that mentions Tcl, it's always been for EDA tools and I run in the other direction.


> Their code sucked like crazy. Usually in such teams you are not evaluated by the quality of code.

I worked in the semiconductor industry, specifically writing internal tools. I had to look at _many_ tools/scripts written in Tcl/SKILL/perl/awk/sed with some code being 30 years old and still being used.

Honestly, it wasn't the worse thing. There was usually very little abstraction, the code pretty much always told a story of what the writer was trying to achieve. Naming things was usually pretty bad and you'd get into some pretty gnarly regex/string matching, but at least it usually wasn't some over-complicated highly flexible but also restricting in-house framework.


Maybe because EDA users think in terms of hardware description languages like Verilog. When I realized that they have to start with things as basic as clock edges and registering signals it dawned on me that that mindset also lends itself to TCL code unpalatable to SW engineers.


at my last job we used tcl for manufacturing tests automation. it was way better than the dsl they previously had. tcl was chosen because it was easy to port an interpreter to their c++ test automation framework


Siemens NX uses it for the CAM post-processor customization.


What are your thoughts? How do you like it as a language?


I found Tcl hard to write, read and debug. (But I'm quite impressed with some of Ousterhout's writing.)

In contrast, Python is easy to read, write and debug.

I quite like the small footprint of Lua as an embedded script language, which is easy to read, write and runs relatively fast.

I'd probably use Lua or a Scheme as an embedded scripting language if I had the need (generally, I try to not let my programs get so big that the require embedded scripting, but instead attempt to create orthogonal CLI commands that do one thing well and that can be scripted from the outside via POSIX sh).


Everyone likes lua but out of the box it's pretty poor for string manipulation, a particular strength of tcl. I don't consider them to have much overlap for that reason.

Lua's match system is cool for what it is, and how small its implementation is. But it's really not powerful in the end and lua has almost no other string tools in the standard lib.


lua has decent string lib in the stdlib, you can also use 3rd party libraries such as https://github.com/stein197/lua-string, I'm not familiar with tcl, can you elaborate why tcl is much better than lua in string manipulations?


Lua has a very minimal string library that doesn't include basic things like escaping and interpolation. It doesn't include regex. I understand the choices behind these decisions and don't disagree with them. But what it means is now you're not just embedding lua, you're embedding lua, vendoring extensions to the standard library, and selecting a regex implementation compatible with your expected runtime and distribution method.

Tcl is built around string manipulation, it's the main and, for a long time, only data type. It has regex built in, a full set of string manipulation functions, and an interpolation primitive.

It's not about what's better. They are both full blown programming languages and you can implement any behavior you care to. In lua you have to care to implement string functions if you're going to do anything complex with strings. Tcl comes with them. That's all.


Although Lua patterns are quite elegant and, in my opinion, more readable than normal regex. Despite having a small string library, Lua still works for many use cases.


Not OP, but I think the reasoning behind that argument is: TCL has only one data type: string. Everything you do in TCL is reduced to string manipulations up to having optional function arguments starting with a - like a shell command. A string in TCL is like a list in Lisp, the most fundamental building block.


In tcl strings are first class citizens as everything in tcl is a basically a string.


It's weird, I spent 3 years working on one of what must have been the largest TCL codebases in existence, since then I've reached for the language exactly zero times in personal or professional use. I have very little fondness for the language.


I think it is extremely cool as a toy language. I've loved learning it for fun. I absolutely never ever would want to have to use it to do something productive


In the 1990s the services in AOL were all written in C, using a very very good event based callback library, which had Tcl embedded in it. We had a very rigorous standard of nothing in the server was hard wired, any change that ops might want to make would be applied to a running server, and dev had the job to expose all configurable state as either Tcl Vars or Tcl commands. It was a no thread environment (one process per CPU and one CPU for the OS) so while your Tcl command was running you could do anything to the in memory structures you wanted as long as it was fast. The ability to have a Turing complete language available for ops to do config in was extremely useful, they wrote amazing Tcl making configuration on the fly.

Not sure any of it would have carried thru till today even if the business hadn’t stumbled, but it very productive in the 1990s.


This is a more interesting take to me than others since it seems you’ve actually learned it?

I see the response later that you’d rather use whatever the team already uses, but could you explain this very strong opinion?

I maintained a 200,000 loc TCL codebase running the front end for millions of lines of industrial C spread over hundreds of machines. It was glorious. After moving on, I’m still struggling to figure out why TCL is so unpopular outside that domain. Other comments seem to boil down to not understanding the language or how to apply it. So what’s your take?


That does sound glorious. And, to be clear, I am merely an amateur (lover of) rather than experienced practitioner. It’s also the only lisp-like I’ve learned, and it gets a fair amount of credit from me on those grounds alone. At any rate, the opinion is very strong because 1) the pragmatic angle that I’ve already mentioned, 2) the tooling hasn’t kept up, 3) the performance isn’t worth fighting for (although it’s really still fairly good), 4) the footguns are gloriously just around the corner.

I don’t know if all lisps suffer from that last one, and I don’t know about you, but there seems to be a clever solution hiding in every line. I think it would take discipline to have a codebase that remained cohesive and in a “single language”


What would you use instead?


For almost all the things I would use tcl for, I would instead pick whatever scripting language my team happens to be most comfortable with, with an eye on any larger company norms. In my various organizations, this has usually meant one of:

- python

- nodejs

- bash

Of those, I personally would prefer bash as the “organizer of commands,” but that’s probably not surprising from someone who would learn tcl for fun.

Usually, the use case for these things is some combination of gluing programs together or gluing programs to the specifics of their OS. In that regard you might think, that picking a language specific for the problem at hand would be wise. But pragmatically, I believe you want devs who are mostly focused on feature work in their own programs to nevertheless be able to “bring their head above the water” and still have immediate intuition about how to swim. So even though nodejs might suck for this, it’s also extremely approachable for somebody who spends all their days in js.

I am really drawn to the idea of doing all of this in something like nix, but I tried it and didn’t have time to get past the learning curve. And that points to the other issue:

You’re probably going to need to glue rpm scripts, Dockerfiles, specific system tools which have different amounts of portability, etc. At the end of the day, if there’s a “lingua franca” for the organization, it usually makes sense to use it so that nobody needs to spend any time becoming accustomed to the idioms of some unfamiliar language while they are simultaneously dealing with the unfamiliar terrain of dealing with other systems that they have very incomplete knowledge of.

If there’s no lingua franca, I default to bash as a larger lingua franca


I think the comparison to bash etc is apt.

tcl is definitely much better, but equally uninspiring.


More like PHP in my mind.


same. Back in ~2009 I had the pleasure/horror of debugging/fixing "TCL drivers" for a piece of Java software that aimed to integrate with various pieces of hardware. I'll never risk putting anyone else through a similar torture if I can help it. ...then again I've left a trail of Ruby/Rails behind me that might get the same job done :)


For a company in the rental automobile space?


The Python and Tcl programs don't do the same thing. The Python output is in most-common order, which requires a sort. The Tcl one is in arbitrary order.

The idiomatic Python matching what the Tcl code does is:

    #!/usr/bin/env python

    import collections
    import sys

    for p in sys.argv[1:]:
        try:
            with open(p) as f:
                c = collections.Counter(f)
                for k, v in c.items():
                    if v > 1:
                        k = k.rstrip("\n")
                        print(f"{v}\t{k}")
        except Exception as e:
            print(f"dup: {e}")
            continue
Except, even then there are three differences:

1) the Tcl code accumulates the counts across all the files, the Python code resets each time. Though the Tcl code reports the counts after each file???

2) the Tcl code has a file descriptor leak (one of the big issues I have with Tcl is in dealing with resource management. I've used an upvar with a delete trace to emulate local scope, but that makes passing the result upstream tricky).

3) the Tcl code includes the empty string "" after the final newline in the count, which doesn't make sense.

To show the issue, I've replace the Tcl code so it outputs quotes around the line:

                puts "$n\t'$line'"
and set the file descriptor limit to 10 before having it read a file containing only two lines, each containing "A":

  % printf "A\nA\n" > x
  % limit descriptors 10
  % tclsh dup.tclsh x x x x x x x x x x x
  2 'A'
  2 ''
  4 'A'
  3 ''
  6 'A'
  4 ''
  8 'A'
  5 ''
  10 'A'
  dup: couldn't open "x": too many open files
  dup: couldn't open "x": too many open files
  dup: couldn't open "x": too many open files
  dup: couldn't open "x": too many open files
  dup: couldn't open "x": too many open files
  dup: couldn't open "x": too many open files
  % python dup.py x x x x x x x x x x x
  2 A
  2 A
  2 A
  2 A
  2 A
  2 A
  2 A
  2 A
  2 A
  2 A
  2 A


How about some PowerShell instead?

# Echo command-line input

    $args -join " "
(Yes, it's a single line.)

# Count duplicates in a file

    $lines = @{}
    foreach ($path in $args) {
        cat $path | % { $lines[$_] += 1 }
    }
    $lines.GetEnumerator() | % { if ($_.Value -gt 1) { "$($_.Value)`t$($_.Name)" } }
# GET request

    param($url)
    irm $url
# Parallel GET requests

    $before = get-date
    $args | % -parallel {
        $res = iwr $_
        $after = get-date
        $time = ($after - $using:before).TotalSeconds
        "${time}s`t$($res.RawContentLength)`t$_"
    } -ThrottleLimit 12345
    $after = get-date
    $time = ($after - $before).TotalSeconds
    "${time}s elapsed"
I'm showcasing a few different ways of doing things: using $args, using the param directive; using foreach, or % which is an alias of ForEach-Object; irm for Invoke-RestMethod, iwr for Invoke-WebRequest...


No thanks. PowerShell seems to have all sorts of crazy gotchas in addition to the weird syntax.

For example, if I understand correctly, your "cat $path" won't actually work for all filenames, because "cat" is an alias [1] for "Get-Content -Path" which will expand wildcards in the filename [2][3], so you have to know to use "-LiteralPath" instead of you want it to work properly.

And then there's Microsoft's telemetry, which is enabled by default. [4]

[1] https://learn.microsoft.com/en-us/powershell/scripting/learn...

[2] https://stackoverflow.com/questions/33572502/unable-to-get-o...

[3] https://learn.microsoft.com/en-us/powershell/module/microsof...

[4] https://learn.microsoft.com/en-us/powershell/module/microsof...


“<Language> seems to have all sorts of crazy gotchas in addition to the weird syntax.”

Says everyone about every language.


A shell language that expands wildcards isn't what I'd call a "crazy gotcha". And let's be real, if you name your files with * or ? in their names, you're just setting yourself up for a bad time.

Weird syntax? Weirder than TCL?

I frankly don't care about the telemetry.


Reminder that TCL was programmed at a time when SPARCstation IPXes with a maximum of 96MB RAM were being used.

Some of the lower-end SPARC machines could only have 16 or 32MB RAM and had perhaps 10 to 12 MIPS of computing power.

Expect, which became a very important extension of TCL, was released in 1990, when the SPARCstation 1+ with about 15 MIPS and max RAM of 128MB was introduced.

Thus any extension language like TCL had to be small and efficient; especially when you consider it was designed to be embedded inside another program.


As someone is pointing above Tcl is nowadays used as the shell language for many EDA tools for which it is normal to consume multiple TBs of RAM.


Every few years I use Tcl/Tk and the most common reasons are:

1- It's easy to make an executable for Windows (as well as running it on Linux);

2- It's easy to write a GUI.


And there's nothing special about Tcl the language that makes it special for scripting GUIs -- some people just put in the work to make a cross-platform GUI library and now everyone using Tcl benefits from it. I really wish Go had something similar to Tcl/Tk. I know making a cross-platform GUI library is hard, so how did Tcl/Tk's authors do it? All the way back in 1991, which still works even today?


Sun put money into it in the mid 90s, it was their big cross-platform project before putting everything behind Java.

see https://www.tcl.tk/about/history.html#Sun


Yeah, exactly what I used it for. I used to make small one off gui utilities for my team to use to save time doing certain tasks.


Its easy to develop, surely, but my style feeling hurts how ugly TCL GUI looks like compared to modern GUIs


Given the recent trends in "UX" I would regard that as a a feature though.


Yep, what I've always appreciated most about Tcl is ease of using Tk.

Worst way to make a scripted GUI: dtksh


TCL is a primary language for sqlite testing: https://www.sqlite.org/testing.html

I never liked TCL because the string quoting rules were almost as bad as Shell's. For small scripting I prefer Lua.


Similarly, Redis uses TCL for unit testing.


Well Redis started off being written in TCL.

https://gist.github.com/antirez/6ca04dd191bdb82aad9fb241013e...

And of course Antirez has a soft-spot for TCL:

http://antirez.com/articoli/tclmisunderstood.html

Which inspired me to create a (trivial) TCL interpreter in golang. Not perfect, but almost as good as picol:

https://github.com/skx/critical


    foreach line [split $file \n] {
            incr count($line)
        }

       foreach {line n} [array get count] { 
            if {$n > 1} {
                puts "$n\t$line"
            }
        }
......

IMHO that's hard to grok. count shows up in the middle of a loop and it's not clear to me what it is. Could be a function, an integer, a map, or an array.

Same problem with this:

>foreach {line n} [array get count]

Not really obvious what any of this does. Guessing it's taking a map, converting it to key/value array, and iterating.


> Could be a function, an integer, a map, or an array.

It is an array. 'incr' is a built in function that "increments" a variable by one.

Variable names followed by parenthesis () are Tcl arrays. So count(...) is "array syntax".

> Same problem with this:

> >foreach {line n} [array get count]

This is roughly equivalent to Python's "list comprehensions". "array get count" produces a list of values from the contents of the array named count (the list is "key1 value1 key2 value2 key3 value3 ..."

foreach is Tcl's "iterate over a list" function. In this case it is iterating two variables over the list at the same time taking two elements off the list each time. The first element is assigned to "line" the second element to "n".


> IMHO that's hard to grok.

Keep in mind that the default way C does for loops is the most un-intuitive way of doing a for loop. This Tcl example is less alien than the C for loop is for someone who has programmed in non-C like languages.

You learn it, and you get used to it.


    for dish, price := range menu {
        fmt.Println(dish, price)
    }

    for key, value in menu.items():
        print(key, ' is ', value)

    for (key, value) in hashmap {
        println!("{} {}", key, value);
    }
Really, it seems like that's a common syntax for languages with first-class dictionaries.


Those have an "in" keyword that makes it much more obvious what's going on. And none of them have the "count" variable that pops up in a loop but is somehow accessible outside it.


In python variables introduced in a loop are definitely accessible outside of it...


Because you work in semiconductor industry.


I only came across TCL as the scripting language used internally by the F5 (BigIP) load balancer. Writing irules was fine because they are usually one and done. It was tempting to try to do complicated things until you realized that was exactly the wrong place to try to fix the underlying problems of a broken web application.


Is Tcl widely used in semiconductors because of a particular thing it does well or is it mostly because of inertia? I don't know a lot about the language so I'm curious :)


The language originated as a common language for semiconductor design tools.

Ousterhout saw that a number of design tools he was involved with all had ad hoc languages, so he developed Tcl as a single common solution to the 'scripting language problem'. This is partly why the language is designed to be as easily embeddable as it is.

Post this origin, Tcl developed a modicum of broader fame through Tk (one of the best/easiest early X11 toolkits) and expect (which is a tool for automating control of command line interactive tools).


Or have an ancient and undying passion for Eggdrop IRC bots. Eggdrop was an introduction to TCL for dozens of us.


The best thing about TCL is it's not OCEAN.


No Perl comparisons? I must be a dinosaur, but Perl is the best for reading in text, regex matching/search/replace/, and outputting a transmorgified result.


Came here to say this. Probably the comparison with Perl is the only one that matters in this context.


The only two times I've touched something remotely tcl was - working on Metal Gear Solid's port from PS1 to PC - it used TCL-like scripting language for the levels, functions were main-like, e.g. "Chara_Init(int argc, const argv[], / something else here */)", and then it was quickly parsing arguments (like tcl) to initialize initial state and types of characters/items/etc.

The second one was through Maya, if you can call maya's language tcl.

It was fascinating to me back then, but nowadays I'm leaning more and more towards static typing.


This is an amazing - I remember that PC port and you can even buy it on GOG today.

Do you have any interesting stories from the porting process?


  > Ousterhout's dichotomy claims that there are two general categories of programming languages:
  > 
  > low-level, statically-typed systems languages
  > high-level, dynamic scripting languages
I understand that this may have seemed somewhat more true at the time it was originally stated, but it's not really been true since the '90s, and it's certainly not true now. What an odd thing to lean into in this day and age.


Are you saying the lines are blurred, or that there are more tiers of abstraction?


It's all shades of grey. Look at C#, it's a statically-typed high-level language which runs in a VM, but can also be compiled to native code. It has a massive standard library and can do all sorts of script-y things (see eg, top-level statements), but can also be tightly optimized to avoid GC allocations. You can mess with pointers in unsafe blocks.

The only thing it really can't do is kernel code.

Even C++ is incorporating more and more high-level constructs.


The general principal of alternating soft and hard layers is still a useful design tool. Even starting from silicon with the microcode emulation of the x86 instruction sets, kernel, syscall interface, building an app and exposing configuration, whether into Tcl, yaml, tool, .properties, json or xml, it’s very likely the consumers of complex software will want to configure its characteristics into a specific use case, but the implementation will be more general. Look at LLM as he ultimate soft layer.

Unless your team manages to never write YAML files.


I wonder how many folks learned of tcl/tk cause of eggdrop.


I learned it originally because of Xircon: https://en.wikipedia.org/wiki/XiRCON


As others have said, Tcl has some unique advantages.

My preferred one, which I have not found in other languages (besides LISP, obviously): treat data as code.

This way your configuration files are Tcl code using some proc's that you have defined, but they can also include arbitrary code. In order to read them, you simply fire up a slave (safe) interpreter and get the results.

If anyone knows how to do this easily in Python, I'm all ears!


I mean, if it's just Python code as config for a Python, couldn't you just write your config in a Python file and include it?

Typically the reason for not using a full-blown programming language for config is to avoid Turing creep - the tendency to want to push complexity into config in order to make the application code simple. At some point the config gets to complex it needs its own config.

I run into this temptation using Lua a lot. Lua tables are perfect for config, but it would also sometimes be so convenient to just pop on a metatable and turn it into a class, maybe a proper API, when really all I need is a table that defines constants, maybe some conditionals, arrays, a map and that's it.


I like having a config file like:

    host thatmachine.example.org
    port 9991
    dbhost server.example.org
... which I can definitely parse myself. But it's also valid Tcl, if host/port/dbhost are defined proc's!

So this allows people to be able to create config files like:

    set mydomain example.org
    host thatmachine.$mydomain
    dbhost server.$mydomain
    if {$mydomain == example.org} {
        port 12345
    } else {
        port 9991
    }
I don't expect people to create huge programs in their config files, but being able to give them a "full-blown programming language" for free is sometimes desirable.


Python has nothing at all like safe interpreters (I've looked). You just can't prevent things from leaking, it isn't designed for it at all. You can do a half-hearted approach by specifying the globals dictionary to eval(), but you can't really count on safety because the builtins do the leaking and you can't really stop anyone from finding a way to get back to that; the language is just too deeply interlinked for a proper security boundary to be erected anywhere inside it.


This brings back memories. I used TCL/Expect for automating 3rd party software we were told could not be done. TCL/Expect: Hold my beer!


I've actually found some use for it as a lisp-ish shell scripting language, when the project grows too large for a bash script. In cases where there's a lot of interaction with the OS and a lot of text processing that has to be done on the results, python just gets in the way and TCL is well suited for that.


Honestly I wish all the old awk fanatics had been tcl fanatics instead.


I have a tcl/tk distribution (now IronTcl 8.6.7) with tcllib on each computer I use since a lot of time. It's very handy : md5 and other hash tools ? tcllib. Quick and simple representation of data as graph and calculations ? tcllib. Simple data structuration ? Tcl dicts. String search/substitutions/regex ? pure tcl. batch operations on files/folders ? pure tcl. Easy and fast database with sqlite ? Tcl sqlite lib. Custom menu and app launcher in your desktop ? Pure Tcl. Hex and binary file reading and formating ? Pure tcl. And so on. As a public administration worker, Tcl Tk helped me working very faster for a lot of tasks.


The article is so laughable.

1. Of course "Hello world" takes more lines in Go than Python or Tcl. But that does not mean anything. The "overhead" here will become meaningful for larger scripts/projects, and Go or Python are far more capable of much more complex things than Tcl. This is simply a dumb example.

2. Other than certain circumstances, I never heard any one care about scripting performance on the ms level. Often not even at 1-2s (which rarely happens anyway). Putting a table out there and say "this is faster by <1ms" is meaningless to anyone other than the author.


I just remembered the Decent Espresso machine has its UI written in tcl/tk https://decentespresso.com/


My biggest question about TCL--pronouncing it: T C L (each letter pronounced on its own) or tickle. I've heard it pronounced both ways by different programmers


https://wiki.tcl-lang.org/page/How+do+you+say+%27Tcl%27

> It was a good decision to invent tcltest rather than, say, "testtcl". Or, better yet, do not say the latter, at least in English.


For me for Tcl to be relevant again its needs

1. a Package Manager (they sort of already have one called TEA, but its not active as far as I know)

2. an IDE or a good modes for the popular editors (VS Code, Emacs, Vim, Sublime Text)

For a UI language the absense of a good IDE or editor modes is very strange, you would expect that a Tcl/Tk IDE written in Tcl/Tk (one that also serve as UI framework or scaffolding for other apps) would be its killer app, but there isnt one


The main issue with supporting those IDEs is that the Language Server Protocol is vast and really quite complicated. More than a weekend's work to go to doing the interesting bits so life gets in the way...

The IDE tools for Tcl have tended to be commercial and to not see much general adoption. Never understood why.


The author should investigate their networking setup. I can run their Go fetchall example in no more than 70ms. Their benchmarks are surely dominated by noise.


Because you are forced to. That's the only reason.

Otherwise please use something modern and sane like Deno, Python, Go or even Rust.

TCL has many large flaws that those languages don't share:

* No static typing so trivial errors are not caught early and code is difficult to understand and navigate.

* Everything is a string (TCL people try to claim otherwise but semantically it really is true). This leads to typing mistakes too.

* Quoting is a mess. It's simple in that the implementation is simple, but languages shouldn't optimise for that alone. Actually getting quoting right in TCL is ... well it's not as bad as Bash or even YAML but it's still something you have to think about, which isn't true for any of the other languages I listed.

If you need a language to embed in your program then while I'm no Lua fan, it is at least significantly better than TCL.


Almost all quoting for non-trivial cases comes down to the [list] command. It does the Right Thing, especially in the critical cases where you're generating a command to call later. Pretty much everything else is either much simpler to the point of needing no thought at all, or complex enough that you're best off writing a command to do it properly (e.g., because you're generating JSON or CSV or some other language like that) as you'll want to test it thoroughly too.

It's hard to be much more specific than that without talking detailed examples.


my espresso machine has software written in TCL and within the group of owners/hackers it's quite the bit of drama how much everybody regrets the original developers choice for it and his insistence it is still a good choice. personally, i value language aesthetics highly and TCL is an ugly one.


Decent proved that they can't do a good job at software and don't really have a vision or intention to do well. The owner's proposed "fix" for TCL is a rewrite using "vanilla javascript" using little or no libraries. What?


i just don't get it, considering they are basically completely relying on unpaid labor from the community to get even the TCL app front end working. the defaults are terrible and life became so much better after the mimoja-cafe frontend came out.


Yeah, and most of the maintainers have largely quit now because of abuse from John Buckman. I think Mimoja contributes occasionally but not in a large capacity.


One thing about scripting languages is that it doesn't seem like a hugely meaningful choice anymore.

GPT-4 can easily generate trivial scripts in any language.

Any complex codebase can be maintained in a typical language.


So curious as to why this got downvoted -- it feels exactly right.


Tech type people that previously found a massive value in producing simple scripts probably wouldn't like this. It erases the value of the skills they developed.

Only a professional programmer is insulated from this obsolescence.

I am sympathetic to the fact that low-knowledge tech workers had their value erased over night. I don't have any solutions for this problem.


Any sufficiently complex LLM-generated program is (semantically) indistinguishable from garbage.


I've found that GPT4 is particularly good at generating TCL code. Possibly because what's out there is generally high-quality, possibly because there's very little syntax for it to keep track of.


GPT-4 is fantastic as one-off scripts for any task.

It can produce scripts in seconds that might have taken technical support people an entire day (stretched to a week or more of work) prior.

Of course, it can't maintain a codebase like a professional developer can. Thank God.


Well duh, that's not what you use it for. You use it to generate parts and snippets quicker.


Sure its good but its a one off script, so who really cares how fast the startup is? Or how elegant it is? I can literally ask LLMs to write a rough working code in seconds and I only need to fix it up a little bit. There's way more resources to find solutions in Python out in the wild that I can leverage.

I would imagine if you just have a tiny bit harder problems, the Tcl code quickly becomes unwieldy and you end up digging through the archives to find some wizard's tome (or maybe there's a large community of Tcl users out there? IDK).

I can easily spin up a GUI in an hour tinkering with PyQt. Can you do the same in Tcl?

DSLs like this tend to have a very nice local maxima. But once you stray away from its original use case you start writing some really confusing code.


"I can easily spin up a GUI in an hour tinkering with PyQt. Can you do the same in Tcl?"

No, it would probably take 10 minutes with Tcl/Tk ;-)




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

Search: