"With" is a construct that can automatically clean up "garbage" after finishing a task. It comes from Lisp. In Tcl it is used in one of two ways: with-new-foo name script or with-existing-foo foo script. In the former case the command with-new-foo creates a foo, sets the caller's variable name to the new foo through upvar, runs the script, and then destroys the foo. In the latter case the command only runs the script and destroys the foo afterwards. It is up to the user to acquire a foo first.
For example, open is usually combined with a chan close. A "with" construct will automatically close the channel (file descriptor). See withOpenFile and Category With Pattern.
Reasonable use of the "with" construct can make your code visibly clearer.
proc getFinalByOp {op opret} { switch -exact -- $op { open { return [list chan close $opret] } default { return [list] } } } # # op: a command # opargs: @op's arguments # body: code that will be executed # ?_finally?: provide a 'finally' yourself # ?varname?: result of @op # proc with {op opargs body {_finally {}} {varname handle} } { set finally $_finally try { set [set varname] [$op {*}$opargs] if {$finally eq {}} { set finally [getFinalByOp $op [set [set varname]]] } eval $body } finally { eval $finally } }
This implementation only supports open, it's not extensive, and it's buggy (see Discussions below).
with open {a.txt w} {chan puts $handle "hello world"} with open {a.txt r} {puts [read $fd]} {} fd with puts {"a test"} {set a {hello}} {puts $a} ;# a meaningless example
Read in a file (employees.txt) of the following format,
name1,salary1 name2,salary2 name3,salary3
The real file is,
Mike Foo,10000 Jack Bar,2000 John Doe,3000
If salary < 3000, add 200, and in the end print the result, write it back.
A possible implementation (can be easier?),
# ... "with" here ... with open {employees.txt r+} { set contents [chan read -nonewline $handle] foreach line [split $contents \n] { set list [split $line ,] if {[lindex $list 1] < 3000} { lset list 1 [expr {200+[lindex $list 1]}] } lappend result "[lindex $list 0],[lindex $list 1]" } chan seek $handle 0 chan truncate $handle 0 puts [join $result \n] chan puts -nonewline $handle [join $result \n] }
From the IRC channel,
<miguel> I am afraid that 'general with' will not work - not with bodies that try to use variables from the calling env <miguel> ... nor with commands that resolv differently in the calling env and in the proc's namespace