I've long wished to have the free time to write a Tcl-derived language, because it really is so elegant in many ways, it just needs a bit of modernization in some areas. It's been years since I really thought much about this but I recall one of the things it's missing is closures (it does have lambdas at least).
Reading through this article, the memoize implementation does have an issue which is if the memoized command wants to call uplevel or upvar it'll get the wrong stack frame. If I were writing this I'd structure it so it's used like
proc myMemoizingProcedure { ... } {
memoize {
... the rest of the code ...
}
}
such that it can just `uplevel` the code. Or better yet I'd make `memoize` replace the `proc` keyword (or perhaps `memoize proc myMemoizingProcedure …`).
EDIT: I suppose memoizing makes no sense in a procedure that wants to use upvar or uplevel though, because memoizing only works for pure functions.
Closures are not easy to fit with "everything is a string", which favors dynamic scoping over lexical scope. I wonder what you'd change to make tcl more amenable to modernization.
Tcl totally has lexical scoping, through proc and apply that create a new frame and thus new local bindings. Closures with explicit bindings (as opposed to tree-walking to find free variables to match with [info locals] which is something Tcl really can't do due to its "list == atom" thing) are easy to do through apply and a way to store the environment.
That last implementation is interesting, although it requires using the `applyc` command to invoke the closure because it has to take it by name instead of by value, which is an unfortunate limitation. The thing you can't do is make a closure that can be invoked just with `apply`. Offhand the only way I can think of to try that is to store the closure data globally under a unique name, but then there's no way to clean up that data when the closure goes away since Tcl doesn't have finalizers (you can run code when a variable goes out of scope, but you can't do it for the value itself).
Yeah, I've been thinking about it for a bit, and the "problem" is that the captured environment needs to be stored somewhere in order to be modified.
applyc could be bundled in the closure to use via `{*}$closure args...` but the fact that it needs to be named defeats the concept of anonymous functions, eh.
I've been toying with building a Tcl-inspired language that basically does away with "{ ... } is just a fancy way to make a string" and treats blocks similarly to how a Lisp treats s-exps - so you could write things like:
type Point struct {
X int
Y lnt
}
Where "struct" maybe could just be a basic macro, but maybe it could also be smart enough to tell the compiler that "lnt" around "Y lnt" is not a type and suggest that maybe you meant "int".
I think a lot of this ground was treaded with modern JS compilers (that had to infer a lot of this kind of information from very little context), or efforts like Python type annotations. Dynamic languages are still cool, and even cooler when the compiler can still somehow actively help you.
Reading through this article, the memoize implementation does have an issue which is if the memoized command wants to call uplevel or upvar it'll get the wrong stack frame. If I were writing this I'd structure it so it's used like
such that it can just `uplevel` the code. Or better yet I'd make `memoize` replace the `proc` keyword (or perhaps `memoize proc myMemoizingProcedure …`).EDIT: I suppose memoizing makes no sense in a procedure that wants to use upvar or uplevel though, because memoizing only works for pure functions.