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 $::fooHey, 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 CoramRS 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 ::foreverNow there are no global variables (kill ::forever by using Tk and falling into the event loop ;-)-- Todd CoramNEM 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.
