FM Expr Shorthands is a proposal to improve the usability of mathematical calculations in Tcl. It consists in :
I took a proc in tcllib, to compute the integral of a function.
proc integral { begin end nosteps func } {
set delta [expr {($end-$begin)/double($nosteps)}]
set hdelta [expr {$delta/2.0}]
set result 0.0
set xval $begin
set func_end [uplevel 1 [list $func $xval]]
for { set i 1 } { $i <= $nosteps } { incr i } {
set func_begin $func_end
set func_middle [uplevel 1 [list $func [expr {$xval+$hdelta}]]]
set func_end [uplevel 1 [list $func [expr {$xval+$delta}]]]
set result [expr {$result+$func_begin+4.0*$func_middle+$func_end}]
set xval [expr {$begin+double($i)*$delta}]
}
return [expr {$result*$delta/6.0}]
}
proc f {x} {
return $x
}
puts Orig:[timerate {integral -2 2 10 f }];
# 39.3619 µs/# 25405 # 25405.3 #/sec 999.988 net-ms
puts length:[(LO=[string length [info body integral]] )]=100%;
# length:588=100%I made 4 variants of this proc whith the proposed shorthand :
The first one is using only the inline shorthand:
proc integralSH1 { begin end nosteps func } {
set delta [( ($end-$begin)/double($nosteps) )]
set hdelta [( $delta/2.0 )]
set result 0.0
set xval $begin
set func_end [uplevel 1 [list $func $xval]]]
for { set i 1 } { $i <= $nosteps } { incr i } {
set func_begin $func_end
set func_middle [uplevel 1 [list $func $xval+$hdelta]]]
set func_end [uplevel 1 [list $func $xval+$delta]]]
set result [($result+$func_begin+4.0*$func_middle+$func_end)]
set xval [($begin+double($i)*$delta)]
}
return [($result*$delta/6.0)]
}
puts SH1:[timerate {integralSH1 -2 2 10 f }];
# 39.3878 µs/# 25388 # 25388.6 #/sec 999.978 net-ms
puts SH1.length=[(L = [string length [info body integralSH1]], "=", 100-$L/$LO*100 )];
# length:558 = -5.102040816326522%We can notice here that there is no practical difference. The inline shorthand [( ... )] is bytecompiled like expr. It is expr.
I create another little variation : as uplevel is waiting for a list, we can write [($func, $xval+$delta)] instead of [list $func $xval+$delta]]
proc integralSH2 { begin end nosteps func } {
set delta [( ($end-$begin)/double($nosteps) )]
set hdelta [( $delta/2.0 )]
set result 0.0
set xval $begin
set func_end [uplevel 1 [($func, $xval)]]
for { set i 1 } { $i <= $nosteps } { incr i } {
set func_begin $func_end
set func_middle [uplevel 1 [($func, $xval+$hdelta)]]
set func_end [uplevel 1 [($func, $xval+$delta)]]
set result [($result+$func_begin+4.0*$func_middle+$func_end)]
set xval [($begin+double($i)*$delta)]
}
return [($result*$delta/6.0)]
}
# SH2:43.7472 µs/# 22858 # 22858.6 #/sec 999.973 net-ms
# length:544 = -7.482993197278915%Here, we can notice it is a little slower : the bytecode analysis shows that it has extra tryCVTtoNumeric. It's because there is a conversion of the list in compileExprTree.
Then I tried TIP282 changes :
proc integralSH6 { begin end nosteps func } {
expr {
delta = ($end-$begin)/double($nosteps);
hdelta = $delta/2.0;
result = 0.0;
xval = $begin;
func_end = [uplevel 1 [list $func $xval]]
}
for {set i 1} {$i <= $nosteps } {incr i} {
expr {
func_begin = $func_end;
func_middle = [uplevel 1 [list $func [expr {$xval+$hdelta}]]];
func_end = [uplevel 1 [list $func [expr {$xval+$delta}]]];
result = $result+$func_begin+4.0*$func_middle+$func_end;
xval = $begin+double($i)*$delta
}
}
return [($result*$delta/6.0)]
}
# SH6:39.2464 µs/# 25480 # 25480.1 #/sec 999.997 net-ms
# length:539 = -8.333333333333343%There is no practical difference also.
Another variant consist in testing body script shorthand {( ... )} capability. I used it on the for command
proc integralSH5 { begin end nosteps func } {
expr {
delta = ($end-$begin)/double($nosteps);
hdelta = $delta/2.0;
result = 0.0;
xval = $begin;
func_end = [uplevel 1 [list $func $xval]]
}
for {(i=1)} {$i <= $nosteps } {(i=$i+1)} {
expr {
func_begin = $func_end;
func_middle = [uplevel 1 [list $func [expr {$xval+$hdelta}]]];
func_end = [uplevel 1 [list $func [expr {$xval+$delta}]]];
result = $result+$func_begin+4.0*$func_middle+$func_end;
xval = $begin+double($i)*$delta
}
}
return [($result*$delta/6.0)]
}
# SH5:40.5876 µs/# 24638 # 24638.0 #/sec 999.998 net-ms
# length:538 = -8.503401360544217%The bytcode analysis show insertion of extra tryCVTtoNumeric. More, there is, more the speed reduce
proc integralSH3 { begin end nosteps func } {
expr {
hdelta = (delta = ($end-(xval = $begin))/double($nosteps))/2.0;
func_end = [uplevel 1 [($func, $xval)]]
}
for {( result = 0.0; i=1)} { $i <= $nosteps } {(i=$i+1)} {(
func_begin = $func_end;
func_middle= [uplevel 1 [($func, $xval+$hdelta)]];
func_end = [uplevel 1 [($func, $xval+$delta)]];
result =$result+$func_begin+4.0*$func_middle+$func_end;
xval=$begin+double($i)*$delta
)}
return [($result*$delta/6.0)]
}
# SH3:49.3772 µs/# 20252 # 20252.3 #/sec 999.987 net-ms
# length:484 = -17.687074829931973%... Same reason.
The last variant is to make the proc as an expression Script : Then it is a lot slower (allmost twice).
proc integralSH { begin end nosteps func } {(
delta = ($end-$begin)/double($nosteps);
hdelta = $delta/2.0;
result = 0.0;
xval = $begin;
func_end = [uplevel 1 [($func, $xval)]];
for({(i=1)},{$i <= $nosteps },{ incr i },{(
func_begin = $func_end;
func_middle = [uplevel 1 [($func, $xval+$hdelta)]];
func_end = [uplevel 1 [($func, $xval+$delta)]];
result = $result+$func_begin+4.0*$func_middle+$func_end;
xval = $begin+double($i)*$delta
)});
$result*$delta/6.0
)}
# SH:71.4310 µs/# 13999 # 13999.5 #/sec 999.963 net-ms
# length:454 = -22.78911564625851%The bytecode analysis shows that the for loop is compiled separatly, in another step. That should be the reason why.
---
gold 3/24/2026. Added categories, so can find message in Wiki.
| Category Numerical Analysis | Category Toys | Category Calculator | Category Mathematics | Category Example | Toys and Games | Category Games | Category Application | Category GUI |