Pipeline programming

SS 2007-03-13:

I like to program in a functional style, so many times I end writing in Tcl and other languages something like [a [b [c [d $arg]]]]. Semantically this is like the Unix shell pipe, so I wrote the following function.

Example:

set s "    supercalifragilistichespiralitoso    "
pipe    {string trim $s} {split * {}} {lsort -unique} {join * {}} > s \
        {string length} > l
puts $s
puts $l

Will output

acefghiloprstu
14

The code:

proc pipe args {
    set r [uplevel 1 [lindex $args 0]]
    for {set i 1} {$i < [llength $args]} {incr i} {
        set e [lindex $args $i]
        if {[llength $e] == 1} {
            if {$e eq {>}} {
                incr i
                uplevel 1 [list set [lindex $args $i] $r]
            } else {
                set r [uplevel 1 [list $e $r]]
            }
        } else {
            set cmd {}
            set substdone 0
            foreach arg $e {
                if {$arg eq {*}} {
                    lappend cmd $r
                    set substdone 1
                } else {
                    lappend cmd $arg
                }
            }
            if {$substdone == 0} {
                lappend cmd $r
            }
            set r [uplevel 1 $cmd]
        }
    }
    return $r
}

set s "    supercalifragilistichespiralitoso    "
pipe    {string trim $s} {split * {}} {lsort -unique} {join * {}} > s \
        {string length} > l
puts $s
puts $l

If you read italian there is a longer article about this concept in my blog at http://antirez.com/post/49


Brian Theado: Nice! I have a slightly different version I use sometimes. Unlike yours it only works for commands that return other commands (i.e. jacl, tcom and tdom are all good candidates). Also, it actually uses a | character as the separator.

proc pipe {cmd args} {
    while {[llength $args]} {
        set n [lsearch -exact $args |]
        if {$n < 0} { set n [llength $args]; lappend args | }
        set a [lreplace $args $n end]
        if {[llength $a]} {
            set cmd [eval [linsert $a 0 $cmd]]
        }
                set args [lreplace $args 0 $n]
    }
    return $cmd
}

I adapted the above code from some version of ratcl (my all-time favorite Tcl extension). It uses the "|" to chain together view operators.

Examples

tdom:

pipe [dom parse $xml] documentElement | firstChild | getAttribute style

# Instead of:
set document [dom parse $xml]
set root     [$document documentElement]
[$root firstChild] getAttribute style

tcom]:
set excel [::tcom::ref createobject Excel.Application]
set worksheets [pipe $excel Workbooks | Add | Worksheets]
set lastWorksheet [$worksheets Item [$worksheets Count]]

# Instead of:
set application [::tcom::ref createobject "Excel.Application"]
set workbooks [$application Workbooks]
set workbook [$workbooks Add]
set worksheets [$workbook Worksheets]
set lastWorksheet [$worksheets Item [$worksheets Count]]

JBR 2009-11-22: I've updated the code above for 8.6 features:

proc | { cmd args } {
    while { [set n [lsearch -exact $args |]] >= 0 } {
        set cmd [uplevel 1 [list $cmd {*}[lrange $args 0 $n-1]]]
        if { [llength [set args [lrange $args $n+1 end]]] == 0 } {
            error "Null command at end of pipeline"
        }
    }
    uplevel 1 [list $cmd {*}$args]
}