[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:
- Redefine proc so that it stores a lambda into a variable.
- Have unknown auto-dereference the the first argument.
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.