Skip to content

Instantly share code, notes, and snippets.

@anticrisis
Last active January 28, 2021 23:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anticrisis/09711a5c8ae4f650507e055a284ec5d5 to your computer and use it in GitHub Desktop.
Save anticrisis/09711a5c8ae4f650507e055a284ec5d5 to your computer and use it in GitHub Desktop.
Pipe, arrow, or threading operator in Tcl
#
# 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
}
}
@anticrisis
Copy link
Author

anticrisis commented Jan 21, 2021

This could be further simplified by moving the subst into pipe. I left it the way it is so one could more easily inspect the string generated by pipe 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment