# Functions as values for Tcl # An example implementation using the 'unknown' function, just # to show the idea in pratical terms. # # Copyright (C) 2003 Salvatore Sanfilippo <[email protected]> # # RATIONALE: # # There is little theoretical justification for functions don't # be strings in Tcl. The only change required to Tcl, is that # the first argument of a command will not be the command name, # but the function itself. # # The following is a Tcl function to add two numbers: # # proc add {x y} {expr {$x+$y}} # # It's used in the following way: # # add 1 2 # # The first argument of the command, is the name of the procedure. # The following, is the same code rewritten to use a function value # as first argument: # # {{x y} {expr $x+$y}} 1 2 # # So, as value, how a function looks like? Just as a list of two elements. # The first element is the argument list, the second the body of the function. # # There is no reason to don't put the function inside a variable. # # set add {{x y} {expr $x+$y}} # # $add 1 2 # # But it can be more handy to define a 'setfunc' function in order # to make the syntax like the Tcl's [proc] command. # # set setfunc { # {varname arglist body} { # uplevel [list set $varname [list $arglist $body]] # } # } # # This way, to set the 'add' variable to the function we write: # # setfunc add {x y} { # expr {$x+$y} # } # # That's as comfortable as [proc] is. # # Still we need to use $add as first argument to call the # function. But a valid function is composed of at least # a two-elements list. So we can add a bit of syntax glue, # and modify the interpreter to just expand the first-argument # of the command to the value of the variable, if the first # argument is a single-element list. # # After this change, we can just write: # # add 1 2 # # To write [lambda] is trivial with the new semantics: # # setfunc lambda {arglist body} { # list $arglist $body # } # # Of course, being functions just values, to assign a variable # to a previoulsy create function is prefectly valid: # # set foo $add # # foo 1 2 # # RESULT: # # We got first-class functions in Tcl. We can write in a very # natural way functions that gets functional arguments, like [map], # or functions that returns functions. Many expects of # functional programming are now natural, and programming # in general is more powerful this way. "Creating abstractions # with functions" was explored for many decades in functional # languages like Lisp. # # With functions as values, [dict], and functions for math operations, # we have a more powerful language, with many of the advantages # of the good Tcl, but more orthogonal, and powerful. If all is # a string, and the idea is to pass stuff around by value, there # is no reason for functions don't behave the same. # # FUTURE WORK: # # Immutable variables can be used to bind variables to functional # arguments that can't be modified after the creation. # [define] may be a good command name. Defined variables with # a functional values (or with any other), are guaranteed to don't change, # allowing for the creation of high-speed Tcl implementations: inlining, # JIT compilers, code specialization, multi-port representation, and so on. # # Another field to investigate is the possibility to have closures. # In such a case the functional representation should be a three-element # list: arglist, body, a closures dictionary. Commands in order to # access/modify the closure's variables from the function should be provided. # # EXAMPLE IMPLEMENTATION: # # The following code implements functions as values in pure Tcl. # The goal of the implementation is to show the idea in # pratical terms to people interested to new directions for the # future of the Tcl language. # # FEEDBACKS # # Please, send feedbacks to <[email protected]> namespace eval funcval {set counter 0} rename unknown funcval::unknown proc unknown args { set e [catch [list uplevel ::funcval::unknown $args] retval] if {!$e} { return $retval } set func [lindex $args 0] set funcargs [lrange $args 1 end] if {[llength $func] == 1} { set x1 {} set x2 {} catch {set x1 [uplevel set $func]} catch {set x2 [uplevel set ::$func]} if {[llength $x1] == 2} { set func $x1 } elseif {[llength $x2] == 2} { set func $x2 } } if {[llength $func] == 2} { set c [incr ::funcval::counter] set t [list proc ::funcval::lambda$c [lindex $func 0] [lindex $func 1]] set e [catch [list uplevel $t]] if {!$e} { set retval [uplevel ::funcval::lambda$c $funcargs] rename ::funcval::lambda$c {} return $retval } catch {rename ::funcval::lambda$c {}} } return -code error "invalid command name \"$func\"" } proc lambda {arglist body} { return [list $arglist $body] } proc setfunc {varname arglist body} { uplevel [list set $varname [list $arglist $body]] }
# A raw example using 'set' set add { {x y} { expr {$x+$y} } } puts [add 1 2] # setfunc is better than 'set' to create functions. setfunc square x { expr {$x*$x} } # Functions are just values, an example setfunc mytest {} { setfunc myadd {x y} {expr $x+$y} set foobar $myadd set x 10 set y 20 puts [foobar $x $y] } mytest # You can pass functions as arguments, an implementation of 'map'. setfunc map {f l} { set res {} foreach t $l { lappend res [$f $t] } return $res } set l {1 2 3 4 5 6} puts [map $square $l] # Anonymous functions are no longer a problem puts [map [lambda x {expr $x-1}] $l] # A more interesting example, # a function foo that takes a number n and returns a function that takes # a number i, and returns n plus i. setfunc foo n { return [lambda i "expr $n+\$i"] } # Usage: set g [foo 5] puts [g 1] ; # => 6 puts [g 3] ; # => 8 # Curring can be used to generalize the previous example. setfunc curry {func arg} { list args "[list eval $func $arg] \$args" } # Now to create the function that adds 5 to the argument we can # just use the curry function. set g [curry add 5] puts [g 1] puts [g 3] # Another currying example, to curry [puts] to create # a version that output to the stderr channel. set eputs [curry puts stderr] eputs {Hello World on stderr} # A one-argument functional composition example # Returns a function that computes [f [g arg]] setfunc compose {f g} { lambda x "[list $f] \[[list $g] \$x\]" } # Example of functional composition setfunc suc x { expr {$x+1} } set suc_square [compose $suc $square] puts [suc_square 5]; # => 26 puts [[compose $square $suc] 5]; # => 36
FW: Note that this is mainly just another Lambda in Tcl implementation. RS: I'd say it's much more - a comprehensive grand tour of many aspects of functional programming that have been discussed in this Wiki over years:
interp alias {} lambda {} list
interp alias {} g {} add 5 ;# vs. set g [curry add 5]