stowroutine

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.