Typing in the algorithm the way you are thinking can be much more fun then change the way you are thinking in order to fit the programming language.
Consider the follwing file: lines.txt
line1 1 2 3 4 5 end-code1-line line2 2 2 3 4 5 end-code2-line line3 3 2 3 4 5 end-code3-line line4 4 2 3 4 5 end-code4-line line5 5 2 3 4 5 end-code5-line
In order to read the text I must open the file first, so actually I want to open the file, then read the text. In Python this would be:
file("lines.txt", "r").read()
which is exactly along my stream of thoughts.
In tcl this would be:
[read [open lines.txt r]]
which is the opposite of my stream of thoughts. I opened the file, then went back on the line and read it.
This is only but a small example, and is so basic that I actually use it as a pattern when reading files. But what if I wanted to extract the middle codeX word of the last word in the last line?
In Python I write:
code = file('lines.txt').read().strip().splitlines()[-1].split()[-1].split('-')[1] print code
Result:
code5
Which is read as: "Open file, read it, strip spaces from beginning and end, generate a list of lines, take last line from the list, generate a list of words, take last word, generate a list by splitting this word at '-' chars, then take second word". That's a one liner! But more important then a one liner, the commands in this line stream the way I think on how to get the result I want.
Converting this line to functional programming is not just hard, worse: It's not fun. One of the things that make Python programming fun is just that: Think and program the same path.
The following procedure makes this possible in tcl, and I call it a Commads pipe, or compi for short:
# Commands Pipe proc compi {sep {fns ""} {var ""}} { # parse arguments if {$var ne ""} { ;# all three arguments supplied foreach {s p} [split $sep ""] {break} } elseif {$fns eq ""} { ;# one argumens supplied set s |; set p ~; set fns $sep } else { ;# two arguments supplied if {[string length $sep] == 2} { ;# first argument is separators foreach {s p} [split $sep ""] {break} } else { ;# sep is fns and fns is var set var $fns; set fns $sep; set s |; set p ~ } } # create a valid tcl command set commands [split $fns $s] set cmd [set curcmd [lindex $commands end]] if {[string first $p $curcmd] < 0} {append cmd $p} for {set i [expr {[llength $commands] - 2}]} {$i >= 0} {incr i -1} { set curcmd [lindex $commands $i] if {[string first $p $curcmd] < 0 && $i > 0} {append curcmd $p} set cmd [string map "$p { \[$curcmd\] }" $cmd] } # perform command if {$var eq ""} {return [uplevel 1 $cmd]} else {uplevel 1 [list set $var $cmd]} }
Using compi:
compi ?sp? commands-pipe ?var?
Now, with this I can write:
compi {open lines.txt r|read|string trim|split~\n|lindex~end|lindex~end|split~-|lindex~1|set code} puts $code
Result:
code5
where each command's output is used as an input to the next command. '|' is a command separator, and '~' is a place holder for previous command's output, and is placed whenever the output is used in the middle of the current command.
The first block of code in compi enables two options (that may be combined): It is possible to choose different chars for the separator and the place holder. This might be needed if the same chars appear in the commands themselves. The previous example could be written:
compi ,` {open lines.txt r,read,string trim,split`\n,lindex`end,lindex`end,split`-,lindex`1,set code} puts $code
If the commands pipe is performed many times, compi will waste time. Hence it is possible to present a variable and then eval it:
compi {open lines.txt r|read|string trim|split~\n|lindex~end|lindex~end|split~-|lindex~1|set code|puts} pipe1
(please mind the last puts)
Where:
puts $pipe1
yields:
puts [set code [lindex [split [lindex [lindex [split [string trim [read [open lines.txt r] ] ] \n] end] end] -] 1] ]
(try writing it straight forward.. ;)
and
eval $pipe1
yields again:
code5
While in Python I can "pipe" commands only when each command is a method of the previous object, with compi there is no such limitation. So I can write:
compi {open lines.txt r|read|string length|puts}
Result:
155
while in Python the string length and printing commands break the path of thoughts:
print len(file('lines.txt').read())
instead of
file('lines.txt').read().len().print()