if {0} {
set a [lambda {a b} { expr {$a + $b} }]
$a 1 2
a 1 2 ;# ambiguous command name "a"...
}21Mar04 Update: I've disabled the unifying of command and variable namespaces by default. It's not a good idea for Tcl I think, as it breaks too many extensions. You can re-enable the functionality by adding "proc" to the export list, and uncommenting a few lines in the funtcl::unknown handler. # funtcl --
#
# Morphs the current interpreter into one based on functional programming
# ideas. Does a number of things:
# - Unifies command and variable namespaces (optional)
# - Defines a [lambda] command for defining first-class function values
# - Redefines [proc] in terms of lambda
# - Supplies some common higher-order functions
#
package provide funtcl 0.2
namespace eval funtcl {
namespace export lambda map filter foldl foldr compose fix curry
#namespace export proc
}
# First up - create the lambda command
interp alias {} ::funtcl::lambda {} list ::funtcl::apply
interp alias {} ::funtcl::curry {} list
# Apply - used for the lambda. This and the next proc go to rather absurd
# lengths to make this work just like a proc - no variables are visible to the
# body of the lambda other than those which are defined by argument passing,
# and any which are retreived through global/upvar etc. The lambda always
# executes in the funtcl namespace though...
proc funtcl::apply {arglist body args} {
# This bit of magic ensures that only args defined in the arglist actually
# get defined (as far as the body is concerned).
ApplyArgs [K $body [unset body]] \
[K $arglist [unset arglist]] [K $args [unset args]]
}
# Sets up variables in the callers scope according to the argument spec given,
# and the actual arguments passed. This should behave exactly like Tcl's
# argument passing to procedures.
proc funtcl::ApplyArgs {body argspec arglist} {
set index 0
array set ret {}
foreach item $argspec {
if {$index < [llength $arglist]} {
if {[lindex $item 0] eq "args" &&
$index == ([llength $argspec]-1)} {
# Deal with remaining args
set ret(args) [lrange $arglist $index end]
set index [llength $arglist]
} else {
set ret([lindex $item 0]) [lindex $arglist $index]
}
} elseif {[llength $item] == 2} {
set ret([lindex $item 0]) [lindex $item 1]
} else {
# Possibly not enough args
if {$item eq "args" && $index == ([llength $argspec]-1)} {
# ok
set ret(args) [list]
} else {
# Build up a good error message
set err "wrong # args: should be \""
# Grab how we were invoked from [info level]
set cmdname [lindex [info level -1] 0]
append err $cmdname
foreach item $argspec {
if {[llength $item] == 1} {
append err " $item"
} else {
append err " ?${item}?"
}
}
append err "\""
error $err
}
}
incr index
}
# Any left over?
if {$index < [llength $arglist]} {
if {[lindex $argspec end 0] eq "args"} {
for {set i $index} {$i < [llength $arglist]} {incr i} {
lappend ret(args) [lindex $arglist $i]
}
} else {
error "too many args to lambda"
}
}
# Add these all to caller's scope
foreach {name value} [array get ret] {
upvar 1 $name n
set n $value
}
# Finally, uplevel the body
uplevel 1 $body
}
# "Fix" a lambda to a proc. Also works with curried commands.
proc funtcl::fix {name lambda} {
uplevel 1 [list interp alias {} $name {} $lambda]
}
# Redefine proc in terms of set and lambda
proc funtcl::proc {name arglist body} {
uplevel 1 [list set $name [lambda $arglist $body]]
}
# Maps a function to each element of a list, and returns a list of the
# results.
proc funtcl::map {func list} {
set ret [list]
foreach item $list {
lappend ret [eval $func [list $item]]
}
return $ret
}
# Filters a list, returning only those items which pass the filter.
proc funtcl::filter {func list} {
set ret [list]
foreach item $list {
if {[eval $func [list $item]]} {
lappend ret $item
}
}
return $ret
}
# Useful higher-order functions which replace common uses of recursion
# foldl (fold left)
# foldl - 0 {1 2 3} -> ((0-1)-2)-3
proc funtcl::foldl {func default list} {
set res $default
foreach item $list {
set res [eval $func [list $res $item]]
}
return $res
}
# foldr (fold right)
# foldr + 0 {1 2 3} -> 1+(2+(3+0))
proc funtcl::foldr {func default list} {
set tot $default
# Go in reverse
for {set i [llength $list]} {$i > 0} {incr i -1} {
set tot [eval $func [list [lindex $list [expr {$i-1}]] $tot]]
}
return $tot
}
# compose - compose two functions together
# [compose f g] $args -> f [g $args]
proc funtcl::compose {f g} {
return [lambda {args} "$f \[eval [list $g] \$args\]"]
}
# The K combinator - obscure, but very useful.
proc funtcl::K {a b} { set a }
# The unknown handler which makes the magic work...
proc funtcl::unknown {cmd args} {
# Uncomment these lines to unify command/var namespaces
#if {![catch {uplevel 1 set $cmd} val]} {
# # It's a varname in callers scope - deref and try again
# uplevel 1 [list $val] $args
#} elseif {![catch {uplevel #0 set $cmd} val]} {
# # Varname at global scope
# uplevel 1 [list $val] $args
#} elseif {[llength $cmd] > 1}
if {[llength $cmd] > 1} {
# Try to expand and try again
uplevel 1 $cmd $args
} else {
# Call Tcl's usual unknown handler
uplevel 1 [list funtcl::orig_unknown $cmd] $args
}
}
# Save the original unknown command for later use
catch {
rename ::unknown funtcl::orig_unknown
# Alias it to our version...
interp alias {} ::unknown {} ::funtcl::unknown
}A few examples of use (with unified command/var namespaces):
% package require funtcl
0.1
% namespace import -force funtcl::*
% set + [lambda {a b} { expr {$a + $b} }]
::funtcl::apply {a b} { expr {$a + $b} }
% # Use the foldr function to make a sum command
% set sum [curry foldr + 0]
foldr + 0
% sum {1 2 3}
6
% # Use compose, to create a command which sums a sorted list (!)
% set sum_sorted [compose sum lsort]
::funtcl::apply args {sum [eval [list lsort] $args]}
% sum_sorted {3 4 1}
8
% # Demonstrate how [proc] is now defined in terms of lambda
% proc foo {} { puts "Hello, World!" }
::functcl::apply {} { puts "Hello, World!" }Well, you get the picture. Everything mostly works as you would expect. There may be some edge cases left still - but this is just a quick fun project! But maybe there are some interesting ideas in here for Tcl 9? Unifying command and variable namespaces is perhaps a bit controversial. Automatic expansion of leading word might be interesting though. Being able to have byte-coded lambdas would also be great!
