4 ms·
Reading this really reminded me of some of the fascinating/crazy things Tcl is capable of, all based on a simple programming model focused on strings. Like, wan
by suyjuris 6y ago
Reading this really reminded me of some of the fascinating/crazy things Tcl is capable of, all based on a simple programming model focused on strings. Like, want to pass a reference to a variable? Simply pass the name of the variable to the function and then do
proc f {varname} {
upvar 1 $varname var
set var ...somevalue...
}
The upvar “links” the local variable var to the variable named $varname in the scope of the calling function. Or maybe you are in dire need of a control construct that loops thrice? No problem:
proc do_thrice {code} {
foreach _ {1 2 3} {
uplevel 1 $code
}
}
Okay, this does not handle break, continue or error propagation (you can do that also, but it would be a bit longer). You can use this like so:
set text Go!
do_thrice {puts $text}
The variable text is not visible inside of do_thrice; but the uplevel executes the code in the scope of its caller, so it can read and write to the local variables just like an ordinary loop.
Of course, in ordinary code you should mostly avoid this kind of stuff. Though apparently uplevel is used by NASA, of all people, so I guess it is fine!
More fun stuff: You use the built-in proc to define a new procedure, but you can replace it by your own, e.g. to insert profiling code at beginning and end. (Hint: If you try this, use uplevel when calling the built-in proc, to avoid namespace problems.) You can parse and rewrite code at runtime (of course) which I once used to insert “breakpoints” at runtime for my program to debug itself.
- doublerabbit 6y agoTCL is one of the few languages allowing you to dabble in to and one of the few that caught hook with me. It's friendliness then leads you down a path of voodoo and creativity. proc displayHackerNews {} { set hn "Hacker News" ;#Fill the variable (hn) with the string "Hacker News" puts $hn ;#output the variable ($hn) to terminal } ;#end procedure displayHackerNews ;#execute the procedure displays: Hacker News If statements are easy set hn "" if { $hn eq "Hacker News" } { puts $hn } else { set hn "Not Hacker News" puts $hn } ;#end if Infinite Loops are fun set hn "Hacker News" ;#Set a variable while { $hn eq "Hacker News" } { puts "$hn" ;# print "Hacker News" to terminal in an infinite loop } ;#end loop Multi-threading is a breeze. package require Thread set MyThreadID [thread::create { puts "Hello from my new thread" thread::wait } ;#end code-to-run ] ;#end thread And finally, a if-switch-threaded-loop set hn "Hacker News" if { $hn eq "Hacker News" } {set switch "hn" } else { set switch "" } switch $switch { hn { set MyThreadID [thread::create { proc displayHackerNews {} {set hn "Hacker News" ; puts $hn} ;#end procedure #set aCounter to zero, if aCounter is < 7 execute displayHackerNews procedure for {set aCounter 0} {$aCounter < 7} {incr aCounter} { displayHackerNews } thread::wait } ;#end threaded-code ] ;#end thread } ;#end switch-case default {puts "It's still not hacker news"} } ;#end switch
- bloaf 6y agoTo really show how much "everything is a string" in Tcl, I love the "sum a list" one liner: set a [list 1 2 3 4] set sumValue [eval [join $a +]] [join $a +] produces a string "1+2+3+4" from the list {1 2 3 4} which you then evaluate to find the sum. Note that you do not need quotes around the +, tcl by default treats everything as a string. Another example: set b a #b now contains the string "a" set a 5 #a now contains the string "5" set var5 Steve #var5 containst the string "Steve" puts $b #write the value of b to the console: "a" puts $$b #writes "5" to the console puts $var$$b #writes "Steve" to the console
- suyjuris 6y agoSome of these things do not quite work this way, but they demonstrate some interesting peculiarities! The eval should be expr, as you are trying to evaluate a mathematical expression, not a command. Similar to the common footgun: set a 1 puts [expr $a + $a] # Wrong! puts [expr {$a + $a}] # Correct Also $$b expands to $a, not 5, as dollar expansion stops at the next non-alphanumeric-or-underscore character. This would usually be written as [set $b] (equivalent to [set [set b]] ), utilising that the set command outputs the value of the variable with that name. Similarly, [set var[set $b]] for the last line. Also, # is only a special character at the beginning of a statement, so comments have to follow a newline or semicolon. In the “everything is a string” department, my favourite are block comments: if 0 { This is a comment. } You can write any kind of comment in there, even special characters like $undefined or [undefined] which would cause errors in normal code. Only wrongly matched {} would be a problem.
- bloaf 6y agomea culpa on the eval/expr. I did the comments inline for brevity. I am confused about the $$ examples though, since swear I have worked on production code that literally behaves like that. Maybe they had some extra non-alphanumeric bits. This behaves the way I gave originally set b a set (a) 5 puts $($b) Edit: I found the production code. There was both puts [expr 3 - $$b] and puts [subst $$b] and the Steve example was actually two steps: set nameVariable [subst var$$b] puts [subst $$nameVariable]