[explain importance of this notion of immutability to proper understanding of Tcl's semantics]
Those who have a little knowledge of C (or other similar languages) have learned to pass arguments to a function by value or by reference.
Tcl scripts have such functionalities : when you need to pass the variable by value, you put a $ before the variable name. And this does not modifiy the original variable.
But when you need to access uplevel variables, you can use upvar in the proc you call, passing just the name of the variable as argument. Then you can access the content of a variable defined in the upper stack level. This feature works especially good with arrays that cannot be passed by value.
But when you come to Tcl C API (the internals that are required to write C extensions), you must know how Tcl passes variables by value, which makes use of the copy-on-write semantics.
Basically the copy-on-write semantics are:
A surprise
Here are 2 procs, the first gets a list passed by value, the second by reference via upvar.
# Proc 1 proc lmul-byval {list mult} { foreach i $list { lappend out [expr {$i*$mult}] } return $out } # Proc 2 proc lmul-byref {list mult} { upvar 1 $list l for {set i 0} {$i<[llength $l]} {incr i} { lset l $i [expr {[lindex $l $i] * $mult}] } return } # Timings proc timeit {lsize val mult} { for {set i 0} {$i<$lsize} {incr i} {lappend list1 $val;lappend list2 $val} puts "By value : [time {set list1 [lmul-byval $list1 $mult]} 10]" puts "By reference : [time {lmul-byref list2 $mult} 10]" } foreach size {100 1000 10000} { puts "TIME : $size elements" timeit $size 2 2 }
Results:
TIME : 100 elements By value : 41.1 microseconds per iteration By reference : 84.0 microseconds per iteration TIME : 1000 elements By value : 386.9 microseconds per iteration By reference : 764.8 microseconds per iteration TIME : 10000 elements By value : 4300.3 microseconds per iteration By reference : 7893.7 microseconds per iteration
As you can see, upvar is costly. Another example :
proc checka {l} { if {[llength $l] == 1} { if {![string is integer $l]} {error "non-numerical value (should be integer)"} return [list bignum $l] } return $l } proc checkb {v} { upvar 1 $v l if {[llength $l] == 1} { if {![string is integer $l]} {error "non-numerical value (should be integer)"} set l [list bignum $l] } }
In the above example, we check a list represents a bignum, and if it is a simple integer, makes a bignum of it.(assuming bignums are lists of integers with a "bignum" heading tag)
foreach i {1 {bignum 2} {bignum 3 3 3 3 3 3 3 3 3}} { set a $i puts "By value: [time {set a [checka $a]} 100]" set b $i puts "By reference: [time {checkb b} 100]" } By value: 2.61 microseconds per iteration By reference: 3.42 microseconds per iteration By value: 2.11 microseconds per iteration By reference: 2.62 microseconds per iteration By value: 2.1 microseconds per iteration By reference: 2.51 microseconds per iteration
This demonstrates that upvar is more time-wasting than passing arguments by value and returning it, for this perticular kind of tasks.
Example in C [and consequences for extension design]
???