PYK 2016-09-15: In the Tcl Chatroom recently, dbohdan mentioned that in Why Ruby is an acceptable Lisp (2005) (randomhacks.net) , pjmlp mentioned working for a startup that had a Rails-like stack, but implemented in a mix of Tcl, Apache and C. This lead me to read Why Ruby is an acceptable LISP , Eric Kidd, 2005, and then to the venerable Revenge of the Nerds , Paul Graham, 2002. Where other languages provide closures, Tcl provides namespaces, which favor "explicit" over "implicit": Each namespace has a name, and its lifespan is under the manual control of the programmer. Below is a procedure, stowroutine, that simply creates a routine that has its own namespace to work in. A helper procedures, stowproc, exposes a stowroutine as a command, and another helper routine, stowsow, plants an existing routine in a new namespace. This suite of procedures accomplishes much the same thing as AMG's sproc, but in a more minimal way. Since an entire namespace is at its disposal, a stowroutine is a flexible creature. One thing to remain aware of is that management of the lifetime of the routine and of its namespace is delegated to the programmer.
A coroutine is of course another natural choice for this sort of thing, and it may even be useful to combine the two mechanisms to create a stowcoro.
#! /bin/env tclsh proc stowroutine {ns argspec body} { list ::apply [list $argspec $body [ uplevel 1 [list namespace eval $ns {namespace current}]]] } proc stowproc {name routine} { uplevel 1 [list interp alias {} [namespace current]::$name {} {*}$routine] } proc stowsow {routine namespace args} { set namespace [uplevel 1 [ list namespace eval $namespace {namespace current}]] set spec [lindex $routine 1] set spec [lreplace $spec[set spec {}] 2 2 $namespace] lreplace $routine[set routine {}] 1 1 $spec }
Example:
set p1 [stowroutine p1 count { variable a incr a $count }] stowproc p1 $p1 stowproc p2 [stowsow $p1 p2] puts [{*}$p1 7] ;# -> 7 puts [p1 8] ;# -> 15 puts [p2 3] ;# -> 3
At first, stowproc looked like this:
proc stowproc {name routine} { uplevel 1 [list proc $name args "::tailcall {*}$routine {*}\$args"] }
However, this folded $routine into a new string, so procedures based on the same stowroutine didn't share the byte-compiled compiled routine. The interp alias variant of stowproc fixes that.