I'm easily amused (especially when amusing myself). See Tail call optimization for other variations.
proc continue: {args} { after idle $args } proc fac {n result {accum 1}} { if {$n > 1} { continue: fac [expr {$n-1}] $result [expr {$accum * $n}] } else { upvar $result res set res $accum } } fac 10 ::foo vwait ::foo puts $::foo
Hey, and there is room for more things to be done while calculating...
proc do_ntimes {cnt code} { uplevel $code if {$cnt > 0} { continue: do_ntimes [incr cnt -1] $code } } fac 10 ::foo do_ntimes 10 {puts "hello world"} vwait ::foo puts $::foo
-- Todd Coram
RS Interesting: this is using the event queue instead of the recursion stack (which has a fixed depth limit - does the event queue have a similar limit?). Functional programmers will of course abhor procedures which do not return a value, as they are run asynchronously, and so mostly global variables will have to be used. But still - another cool braintwister...
DKF: Functional programmers would be better off using a callback (function fragment!) to print the result value instead.
DKF: Here's another variant:
proc calculateFactorial {ary target} { upvar #0 $ary a if {$target < 2} { set a($target) 1 return } if {[info exist a($target)] && [string is integer $a($target)]} { set a($target) $a($target) return } set t1 [expr {$target-1}] set t2 [expr {$target-2}] if {![info exist a($t1)]} { set a($t1) pending after 1000 calculateFactorial $ary $t1 } if {![info exist a($t2)]} { set a($t2) pending after 1000 calculateFactorial $ary $t2 } if {![string is integer $a($t1)] || ![string is integer $a($t2)]} { after 1000 calculateFactorial $ary $target return } set a($target) [expr {$a($t1)+$a($t2)}] }
Enjoy!
(In response to RS) Erlang programmers use a similiar idiom to the one I used at the top of this page. Erlang is (er mostly) functional (no side-effects), but it utilizes message passing. Factorial could be an Erlang asynchronous "process" and the result is simply sent back to the requestor. This would be easy to do in Tcl (and you kinda get that with what I was doing with the event loop!).
A further refinement (as prescribed by DKF):
proc fac {n result_cb {accum 1}} { if {$n > 1} { continue: fac [expr {$n-1}] $result [expr {$accum * $n}] } else { eval $result_cb $accum } } proc notify_me {result} { puts "Factorial result = $result" } fac 10 notify_me vwait ::forever
Now there are no global variables (kill ::forever by using Tk and falling into the event loop ;-)
-- Todd Coram
NEM 5 Feb 2007 offers this cute variant which factors out the callback:
proc spawn {varName = args} { upvar #0 $varName var trace add variable var read [list await $varName] spawn_ $varName $args } proc spawn_ {varName cmd} { set rc [catch { uplevel #0 $cmd } result] switch $rc { 0 { spawn:result $varName $result } 4 { after 0 [list spawn_ $varName $result] } default { return -code $rc $result } } } proc spawn:result {varName result} { upvar #0 $varName var trace remove variable var read [list await $varName] set var $result } proc await {varName args} { upvar #0 $varName var while {![info exists var]} { update } #if {![info exists var]} { uplevel #0 [list vwait $varName] } } proc tailcall args { return -code continue -level 2 $args }
We can then write our factorial function a bit more naturally:
proc fac {n {accum 1}} { if {$n > 1} { tailcall fac [expr {$n-1}] [expr {$accum*$n}] } else { return $accum } } spawn x = fac 100 spawn y = fac 1000 pack [label .l -text "x = "] [label .x -textvariable x] puts "x = $x\ny = $y"
I'd be interested if anyone can get the vwait form of await to work. On my Mac OS X laptop it hangs for ever and never notices the variables being set. I think this may be a bug in vwait.