Last active
January 28, 2021 23:33
-
-
Save anticrisis/09711a5c8ae4f650507e055a284ec5d5 to your computer and use it in GitHub Desktop.
Pipe, arrow, or threading operator in Tcl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# See tryit::go at the bottom of this file for sample. | |
# | |
# pipe $x f g h will evaluate [h [g [f $x]]] | |
# pipe $x {add 5} {mul 3} will evaluate [mul [add $x 5] 3] | |
# | |
namespace eval util { | |
namespace export pipe | |
proc pipe {args} { | |
# Return a script representing a sequence of nested commands. | |
# pipe x f g h -> [h [g [f x]]] | |
string trim [to_command [list [arrow {} {*}$args]]] | |
} | |
proc arrow {acc args} { | |
# Worker for pipe. | |
# Returns arguments in right-to-left order, nested, appended to acc. | |
# | |
# arrow {} x f g h -> {h {g {f x}}} | |
# arrow {} x {f a} {g b} {h c d e} -> h {g {f x a} b} c d e | |
set largs [llength $args] | |
if {$largs == 0} { | |
return $acc | |
} else { | |
set head [lindex $args 0] | |
set rest [lrange $args 1 end] | |
set head_head [lindex $head 0] | |
set head_rest [lrange $head 1 end] | |
# splice acc into head unless its empty | |
if {[llength $acc] == 0} { | |
set splice [list $head_head {*}$head_rest] | |
} else { | |
set splice [list $head_head $acc {*}$head_rest] | |
} | |
if {[llength $rest] == 0} {arrow $splice} else {arrow $splice {*}$rest} | |
} | |
} | |
proc to_command {l} { | |
# depth-first descent converting lists with one or more words into | |
# commands. On initial entry, L must be a list with a single | |
# element. | |
if {[llength $l] == 0} {return $l} | |
set head [lindex $l 0] | |
set rest [lrange $l 1 end] | |
set h [lindex $head 0] | |
set t [lrange $head 1 end] | |
if {[llength $t] > 0} { | |
string cat "\[" $h " " [to_command $t] "\] " $rest | |
} else { | |
string cat $h " " $rest | |
} | |
} | |
} | |
namespace eval tryit { | |
proc f {x} {expr {$x + 1}} | |
proc g {x} {expr {$x + 2}} | |
proc h {x} {expr {$x + 3}} | |
proc go {} { | |
subst [util::pipe 1 f g h] ;# => 7 | |
} | |
proc add {x y} {expr {$x + $y}} | |
proc mul {x y} {expr {$x * $y}} | |
proc go2 {} { | |
subst [util::pipe 1 {add 5} {mul 3}] ;# => 18 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This could be further simplified by moving the
subst
intopipe
. I left it the way it is so one could more easily inspect the string generated bypipe
before doing command substitution. The code could use some cleaning but it works.At each command, the result of the prior command is inserted into the first argument place. This is like clojure's thread-first (->) macro. Arguably the thread-last (->>) macro would make more sense for Tcl, because then each command would effectively be a "command prefix," a concept that is used in various other Tcl and Tk APIs. This implementation is left as an exercise for the reader.