set f(x y)would assign the value "y)" to a scalar variable named "f(x" ! Instead, you call such a function with e.g. two arguments like this:
puts $f($x,$y)The first dollar sign substitutes it as a variable (which it really is), but the associated trace computes the function value, if the arguments are not an array element, as implemented in the few lines of code below. A function definition is modeled like a proc definition (no default values or args, though). We observe the following features:
- the body is evaled when necessary, hence not byte-compiled, hence slower
- previous results are cached (see Result caching), which may save time on lengthy calculations
- the cache will consume (but not leak!) memory if the function is called often with different arguments
- variable scope applies: functions defined at global scope must be marked as such when called inside a proc
- otoh, functions defined in a proc are truly local: not visible from elsewhere (except via uplevel); the array as well as its trace disappear on return from the defining proc
proc function {name argl body} { upvar 1 $name f array set f {} ;# make it exist (but add no elements) trace variable f rw [list runFunction $argl $body] } proc runFunction {argl body name el op} { if {$op=="w"} {error "can't set function value for $name"} upvar 1 $name f if {![info exists f($el)]} { foreach $argl [split $el ,] break set f($el) [eval $body] } else {set f($el)} } # Testing... (expected results in front of each puts) #--define an array-based function function sq2 {x z} {expr {$x*$x + $z}} #-- try to call it from global scope puts "42? $sq2(6,6)" #-- try to call it from inside a proc proc test1 {} {puts $sq2(7,7)} catch test1 res puts "error? $res" ;# should raise "unknown variable" error #-- try again, but marked as global this time proc test2 {} {puts "56? $::sq2(7,7)"} test2 #-- try a function defined locally inside a proc proc test3 {} { function local {x y z} {expr {$x*$y-$z}} puts "5? $local(3,2,1)" } test3 #-- try to call it from global scope catch {set local(4,3,2)} res puts "error? $res" ;# should raise the same error
escargo 11/04/2002: I have heard this kind of behavior as memoizing, and it is something used in other languages as well:See the Memorization class I did (Cacheable class) which provides a generic method to cache method results in XOTcl. Works with absolutely anything and provides a good example of how XOTcl's strengths can be used. Warning: hasn't been tested with newer versions of XOTcl where there have been some changes to filters. Please let me know if there's a problem --SetokNote: There was a shift in terminology introduced in your code: What I saw described as memoization you have implemented as memorization. -- escargo 6 Nov 2002
AM I realised that this might be a useful technique for a calculator script I wrote some time ago, as a replacement for the UNIX command "bc" (so that I can use it under Windows too). Its specialty is that you can define "macros", but I would rather use the functional approach. (Mental note: Must wikify it.)
I first thought that functions can even be faster than procs, but the error came because the cache was never reset over hundreds of calls (as typically used with time to get more precise results). Here's a fair comparison, running the test (a simple recursive factorial) just once:
proc pfac x { expr {$x<2? 1: $x * [pfac [incr x -1]]} } function afac x { expr {$x<2? 1: $x * $::afac([incr x -1])} } puts pfac:[time {pfac 30} 1] puts afac:[time {set afac(30)} 1]Solaris:
pfac:950 microseconds per iteration afac:12570 microseconds per iteration