curry

How to curry procedures in Tcl

See Also

interp alias
Custom curry
Hot curry

Description

To curry a function is to produce a new function from it that already has some of its arguments provided, and that can therefore be called with only the arguments that haven't been applied yet. Programming languages such as Haskell incorporate this process as a fundamental tenet that any function that appears to require more than one argument is actually a sequence of functions, each of which accepts only one argument, and which in turn produces another function that only accepts one argument, finally producing a function which accepts no arguments and is evaluated. Such an approach is essentially a straight implementation of the lambda calculus. Other languages, e.g. ML, typically use use tuples instead.

Consider a simple function that accepts two arguments and returns their sum. In Tcl, this is written as a two-argument procedure:

proc sum {a b} { expr {$a + $b} }
sum 1 2 ;# => 3

A curried variant of sum would accept only one argument, returning another command to accept the second argument and produce the result:

proc sum a {
        list apply [list b [format { expr {%d + $b}  } $a]]
}
{*}[sum 1] 2 ;# => 3

lexical closures can simplify the process of currying commands.

One useful effect of the curried style is that it is simple to partially apply a function creating a more specialised function. For example, many higher-order functions like map, fold and so on, can be specialised in this way to create a variety of other functions, e.g.:

sum = foldl (+) 0
product = foldl (*) 1
...

However, such higher-order functions must be carefully designed to accept arguments in the correct order for this to work.

In Tcl, curried functions are seldom (if ever) used, as they are clumsy to construct and perform poorly. Instead, it is more usual to form a command prefix that already includes some of the arguments to be passed to the command when it is evaluated. A command prefix is a list consisting of the command name followed by some optional extra arguments. This can then either be used directly as a callback command or by using the {*} syntax, or can be converted into a normal command using interp alias. For example, given a definition of foldl, we can define sum and product as follows:

proc foldl {f z xs} {
    foreach x $xs { set z [{*}$f $z $x] }
    return $z
}
set sum [list foldl ::tcl::mathop::+ 0]
{*}$sum {1 2 3 4}
# OR:
interp alias {} sum {} foldl ::tcl::mathop::+ 0
sum {1 2 3 4}

PYK 2016-05-22: namespace code can also be used for partial application, but it has the effect of introducing another level. Here is a command that does the same thing, but with a more convenient syntax and without the extra level:

proc partial {cmd args} {
    list apply [list {cmd baked args} {
        tailcall $cmd {*}$baked {*}$args
    } [uplevel {namespace current}]] $cmd $args
}

partial can then be used is such:

{*}[partial sum 3] 4

Another variant can be used to create curried commands:

proc curry {name args} {
    uplevel 1 [list ::interp alias {} $name {} ::apply [
        list {baked args} {
            ::tailcall {*}$baked {*}$args
        } [uplevel {namespace current}]] $args]
}

The reason to use interp alias instead of formulating it as a procedure is that with a procedure, the string representation of $args would have to be generated in order to become part of the body of the procedure.

Example:

proc sum3 x [curry sum 3]
sum3 4 ;# -> 7

partial and curry are available in ycl proc.