[index] [home]

Tweet

First functional steps in Tcl



Recently I'm toying with Tcl (wiki) a bit, and why not take this opportunity to stray from the well-trodden path of imperative programming and try a bit of functional programming for a change.

Ruby has select, reject, collect (a.k.a. map) and inject (a.k.a. reduce) to manipulate arrays. Bare Tcl has no such thing (well, there is lmap), but they are fairly easy to implement, as shown below.

(I realise this code is incomplete and rudimentary; the functions below and/or better versions thereof can likely be found on the Tcl wiki, but right now all I want is to have fun. :-)

select

Select constructs a new list from list-items matching a certain predicate, being a lambda:

    proc select { li pred } { 
        set result {}
        foreach x $li {
            if { [ apply $pred $x ] } { 
                lappend result $x
            }
        }   
        return $result
    }

Example (select items greater than 3):

    set li [ list 1 2 3 4 5 ] 
    puts [ select $li { x { > $x 3 } } ]   ;#   -->   4 5

To make prefix math-operators (like the '> $x 3' above) possible, the following code is used, taken from the 'Concept 8' section in this article:

    foreach op { * / - + > < } { proc $op { a b } [ list expr \$a $op \$b ] } 

(This does not allow for more than 2 operands.)

reject

Reject is the opposite of select: it constructs a new list from list-items not matching a certain predicate. Reject can be written in terms of select by negating the predicate:

    proc reject { li pred } { select $li [ negate $pred ] }

    proc negate pred { return "x { expr ! \[ apply { $pred } \$x ] }" }

Example (reject items greater than 3):

    set li [ list 1 2 3 4 5 ] 
    puts [ reject $li { x { > $x 3 } } ]   ;#   ->   1 2 3

map (or 'collect')

Map transforms one list into another one, by performing the same operation on each original list-item in turn. (I prefer the name map over collect, because it makes clear that an 1:1 mapping takes place between original and newly created list.)

    proc map { li la } {
        set result {}
        foreach x $li {
            lappend result [ apply $la $x ]
        }
        return $result
    }

('la' is again a lambda-expression taking a single argument)

Example (squares):

    set li [ list 1 2 3 4 5 ] 
    puts [ map $li { x { * $x $x } } ]   ;#   -->   1 4 9 16 25

reduce (or 'inject' or 'fold')

Reduce reduces a list to a single value by iterating over the items, and performing an operation on the item-value and an accumulator. (I assume numerical list-items in the following code.)

    # ('la' is a lambda taking 'accumulator' and 'item_val' arguments)
    proc reduce { li la } {
        set accu 0
        foreach x $li {
            set accu [ apply $la $accu $x ]
        }
        return $accu
    }

Example (sum of squares):

    set li [ list 1 2 3 4 5 ] 
    puts [ reduce $li { { accu x } { + $accu [ * $x $x ] } } ]   ;#   -->   55 


Delivered to you by Vim, GNU Make, MultiMarkdown, bozohttpd, NetBSD, and 1 human.