Version 3 of Factorial Using Event Loop

Updated 2003-10-01 08:48:41

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!


[?]