gchung Regarding "Getting rid of the value/command dichotomy for Tcl 9", here's a mock up of what that might look like in Jim.
Jim provides lambdas, which gives us a way to store commands in variables, like so:
set square [lambda {x} {expr $x*$x}] set cmd $square $square 6 ;# => 36 $cmd 7 ;# => 49
I'd like the language to work as follows:
proc square {x} {expr $x*$x} set cmd $square square 6 ;# => 36 cmd 7 ;# => 49
One way to mock up "command as values" in Jim is to:
That's not too complicated. Here's the code:
# We will redefine [proc] so move the original command to [builtin::proc]. rename proc builtin::proc # Redefine [lambda] to use [builtin::proc] instead of [proc]. builtin::proc lambda {arglist args} { tailcall builtin::proc [ref {} function lambda.finalizer] $arglist {*}$args } # Use [unknown] to auto-dereference the first argument of a command list. builtin::proc unknown {cmdname args} { set depth [info level] # Look through the call chain for the command reference. for {set level 1} {$level <= $depth} {incr level} { upvar $level $cmdname cmdref if {[exists -var cmdref]} break } if {$level > $depth} {error "can't read \"$cmdname\": no such variable"} if {![exists -command $cmdref]} {error "\"$cmdname\" is not callable"} tailcall [set cmdref] {*}$args } # Define [proc] - Store a lambda in a variable whose name is the command name. set ::proc [lambda {name args} { # [uplevel] lets us capture static variables in the caller's context. tailcall set $name [uplevel 1 [list lambda {*}$args]] }] # test proc square {x} {expr $x*$x} set cmd $square cmd 7 ;# => 49
Here's another way to define proc which tags the ref with the command name (which I prefer):
set ::proc [lambda {name args} { # [uplevel] lets us capture static variables in the caller's context. # I want to tag the ref with the proc name, so I'm not using [lambda]. tailcall set $name [uplevel 1 [list builtin::proc [ref {} $name lambda.finalizer] {*}$args]] }] # test set x 1 proc x {} {return 2} list [x] $x ;# => 2 <reference.<x______>.00000000000000000002>
Notice that the new proc creates local (not global) commands. Consider the example of a command that generates accumulators:
proc Accumulator {{value 0}} { proc counter {{i 1}} value {incr value $i} return $counter } set c0 [Accumulator] c0 3 ;# => 3 set c1 [Accumulator 10] c1 4 ;# => 14 c0 5 ;# => 8 counter ;# => error: can't read "counter": no such variable
To create global commands anywhere, you'll need to prefix the command name with ::.
Having commands created locally will break a lot of scripts (e.g. Jim's oo package will definitely break).
Now, we can add a bit of power by modifying unknown such that, when the command name is a variable but not a command, we should treat it like a dict and use the second arg as the key to that dict:
# [unknown] version 2: builtin::proc unknown {cmdname args} { set depth [info level] # Look through the call chain for the command reference. for {set level 1} {$level <= $depth} {incr level} { upvar $level $cmdname cmdref if {[exists -var cmdref]} break } if {$level > $depth} {error "can't read \"$cmdname\": no such variable"} # ---code change--- #if {![exists -command $cmdref]} {error "\"$cmdname\" is not callable"} # If cmdref is not callable, maybe it's a dict, search deep into the dict if {![exists -command $cmdref]} { set head $cmdref while {![catch {dict size $head}] && [llength $args]} { set args [lassign $args key] set head [dict get $head $key] if {[exists -command $head]} {tailcall [set head] {*}$args} lappend cmdname $key ;# update the command name for debugging purposes } error "\"$cmdname\" is not callable" } # ---end code change--- tailcall [set cmdref] {*}$args }
Now we can create dispatchers using dicts:
set math [dict create \ square [lambda {x} {expr $x*$x}] \ cube [lambda {x} {expr $x**3}] \ times [dict create \ two [lambda {x} {expr 2*$x}] \ three [lambda {x} {expr 3*$x}] \ ] \ pi $(acos(-1)) ] # test math cube 7 ;# =>343 math times three 7 ;# => 21 math pi ;# => error: "math pi" is not callable
I believe this takes us pretty close to having namespaces and ultimately objects. However, syntactic sugar (such as :: in variable names) needs to be redefined internally to make it usable.