Version 7 of Brace your expr-essions

Updated 2007-01-08 18:28:20

KBK closed a follow-up to the comp.lang.tcl newsgroup with the advice that, "Also, it's important always, always to brace your expressions. Don't do

     set a [expr $b / $c]

but rather

     set a [expr { $b / $c }]

Otherwise, your variables may undergo unexpected conversions numeric -> string -> numeric, you can lose precision, your expressions will be much slower, and you can even have security problems:

     # don't run
     set x { [format C:\] }
     set y [expr $x + 3]

"

AMG: The security problems of unbraced expr expressions very similar to SQL injection attacks. Notice how sqlite's Tcl binding does its own variable expansion to avoid this very problem. Many, many sh scripts have this problem as well because the default is to apply multiple passes of interpretation.


RS: In the following cases braced expressions don't work:

  • operator in variable (which in most languages is impossible, but Tcl can do):
 set op +
 expr $x $op $y
  • (parts of) expression generated by string manipulation:
 expr [join $intlist +]

KPV: Braced expressions also don't work when you do negation in the lazy way:

  set n1 -3
  set n2 -$n1
  expr {2 + $n2} ;# error
  expr 2 + $n2   ;# ok

aricb Dec. 10 2005 -- As a bonus, braced expressions are generally much faster than their non-braced counterparts. Here's an attempt to illustrate the difference:

 proc time_braces {} {

     set expressions {
         {1 + 1}
         {16 - 7}
         {(16 - 7)}
         {7 * 12}
         {81 / 3}
         {81 / 5}
         {16224308 + 123580329}
         {6.3 + 1.2}
         {6.3 - 1.2}
         {6.3 * 1.2}
         {6.3 / 1.2}
         {log(765) * 3}
         {(log(765) * 3) / 8.23}
     }

     puts [format " %-22s  %-8s  %-8s  %-8s  %-5s" "Expression" "-braces" "+braces" "ratio" "% speedup"]
     puts " [string repeat - 22]  [string repeat - 8]  [string repeat - 8]  [string repeat - 8]  [string repeat - 5]"

     foreach expression $expressions {
         set time1 [lindex [time "expr $expression" 100000] 0]
         set time2 [lindex [time [list expr $expression] 100000] 0]
         set ratio [expr {$time1 / $time2}]
         set percent [expr {100 * (1 - ($time2 / $time1))}]
         puts [format " %-22s  %8.6f  %8.6f  %6.3f:1  %5.2f" $expression $time1 $time2 $ratio $percent]
     }

     puts "\n -braces and +braces are in microseconds per iteration"
 }

Results:

 Expression              -braces   +braces   ratio     % speedup
 ----------------------  --------  --------  --------  -----
 1 + 1                   2.334690  0.366480   6.371:1  84.30
 16 - 7                  2.388280  0.359960   6.635:1  84.93
 (16 - 7)                3.748160  0.356120  10.525:1  90.50
 7 * 12                  2.438530  0.368200   6.623:1  84.90
 81 / 3                  2.456060  0.395680   6.207:1  83.89
 81 / 5                  2.518490  0.397380   6.338:1  84.22
 16224308 + 123580329    2.726690  0.369530   7.379:1  86.45
 6.3 + 1.2               3.237970  0.497100   6.514:1  84.65
 6.3 - 1.2               3.199230  0.486350   6.578:1  84.80
 6.3 * 1.2               3.279100  0.480630   6.823:1  85.34
 6.3 / 1.2               3.257540  0.494340   6.590:1  84.82
 log(765) * 3            6.202120  0.975590   6.357:1  84.27
 (log(765) * 3) / 8.23   7.951270  1.142210   6.961:1  85.63

 -braces and +braces are in microseconds per iteration

Arts and crafts of Tcl-Tk programming