[SS] 18Mar2005 This page is a simple introduction to one of the [Jim] features: [closures]. Actually closures in Jim are the sum of many features: * [lambda] * [static vars] with context capturing * [garbage collection] '''Anonymous procedures''' Jim has a [lambda] command that creates anonymous procedures. Actually they are not unnamed at all, just a random name is assigned to procedures crated using the [lambda] command: . set f [lambda x {+ $x $x}] .00000000000000000000> . $f 10 20 . $f 5 10 lambda does not need the ''name'' of the procedure as argument, the name assigned to the created procedure is instead the return value. Unlike normal procedures, anonymous procedures in Jim are ''garbage collected''. You don't need to delete they in some explicit way, as long they are no longer referenced they are automatically destroied. In other words just don't care to free what [lambda] returns and you will do the right thing. '''Static variables''' Static variables are a set of variables relative to a given procedure that are persisent accross calls. In other words static vars are the ''state'' of a procedure. In order to create a procedure with static vars, what is needed is to pass an additional argument to the [proc] command: proc counter {} {{x 0}} { incr x } As you can see just after the ''arguments list'', that is empty in the example, there is the list of static vars associated with the procedure. The syntax used to specify ''default arguments'', here is used to specify the initial value of the variable. Because the variable is persistent to specify the initial value is needed. This is an interactive usage of the '''counter''' procedure: . counter 1 . counter 2 . counter 3 Every time I call the procedure, the static '''x''' is incremented, and the value is returned to the caller. Statics are automatically destroied when a procedure is destroied. '''Context capturing''' In Jim the initialization of static variables is mandatory, but if the initialization value is not provided directly as in the above example, it is "captured" from a local variable having the same name of the static variable. So the above '''counter''' procedure can be written also in this form: set x 0 proc counter {} x { incr x } The initialization value was not specified, so it is obtained from the value of the '''x''' variable that appears in the context where [proc] was called. If no local '''x''' variable exists, an error is generated by the proc command. '''Closures''' Static variables can also be used with ''anonymous procedures'' in exactly the same way. This allows to write the following procedure that '''creates''' a counter anonymous procedure: proc make-counter {} { set count 0 lambda {} count { set count [+ $count 1] } } Usage example: . set a [make-counter] .00000000000000000001> . $a 1 . $a 2 . $a 3 Note how similar the Jim procedure is compared to the scheme version: (define (make-counter) (let ((count 0)) (lambda () (set! count (+ count 1)) count))) In pratice, Jim supports something very similar to lexical scoping, but the semantic is much simpler than the one of the Scheme programming language because the user only need to know about static variables and the feature of capturing a copy from the context to initialize statics. We can make the counter procedure smarter, allowing us to specify an initialization value: proc make-counter initval { set count $initval lambda {} count { set count [+ $count 1] } } Usage example: . set b [make-counter 10] .00000000000000000002> . $b 11 . $b 12 '''More examples''' The following is a more complex example of closure that uses another feature of Jim: arrays are not collection of variables but [dict]ionaries. So an array is just a list with an even number of elements, and you can still use $var($index) form and so on. A very small example before to introduce the new closure example: . set foo [list x 1 y 2] x 1 y 2 . puts $foo(x) 1 . puts $foo(y) 2 . set foo(z) 5 5 . puts $foo x 1 y 2 z 5 It's simple Tcl arrays done The Right Way ;) Of course because in Jim arrays are values like any other thing a Jim static can contain an array (that is, a dictionary). So we can create a '''make-words-counter''' procedure, that returns a closure that is able to count the number occurrences of words: proc make-words-counter {} { lambda word {{dictionary {}}} { if {$word eq {}} { return $dictionary } if {![info exists dictionary($word)]} { set dictionary($word) 0 } incr dictionary($word) } } Now we can create a words counter, and use it to count words: . set d [make-words-counter] .00000000000000000000> . $d apple 1 . $d foo 1 . $d bar 1 . $d bar 2 . $d bar 3 . $d apple 2 . $d {} apple 2 foo 1 bar 3 Called with the empty string the words counter will return the full dictionary. Usually it returns the number of occurrences of the input word. As usually we don't need to care to free it at all. '''Closures as objects''' Closures are after all code with associated data, this is why they are somewhat similar to objects. The following is the usual bank account example done with Jim closures: proc make-bank-account {} { lambda {method args} {{balance 0}} { switch $method { see {set balance} deposit {incr balance [lindex $args 0]} withdraw {set balance [- $balance [lindex $args 0]]} default {error "unknown method $method"} } } } Usage: . set a [make-bank-account] .00000000000000000000> . $a deposit 100 100 . $a deposit 50 150 . $a withdraw 15 135 . $a see 135 Of course it's possible to have multiple bank accounts, and every bank account will be indipendent, and as usually objects done this way are automatically garbage collected.