Version 14 of withOpenFile

Updated 2015-02-19 10:29:37 by PeterLewerin

Peter Lewerin (content disclaimer) 2014-01-11: with open file is an old programming pattern or idiom that abstracts the following:

  1. open a channel to a file
  2. do something with the channel
  3. close the channel

(When I started this page, I first searched this wiki for similar pages, but still missed a couple of pages (keyword search will only get you so far): Playing with with and using, both of which address the same subject.)

In Tcl code, e.g. on this wiki, this procedure is usually written step-by-step, usually without error handling (and sometimes without actually closing the file):

set f [open $myfile]
# ... $f ...
close $f

Abstracting away such sequences will make the code less cluttered. A couple of examples:

proc slurp {filename} {
    set f [open $filename]
    set res [chan read $f]
    chan close $f
    set res
}

proc fileputs {filename str} {
    set f [open $filename a]
    chan puts $f $str
    chan close $f
}

Doing it this way, one command is needed for every file action. Instead, a generic command can be used for all cases where some kind of operation is to be done on a temporarily open file:

proc withOpenFile args {
    if {[llength $args] < 3} {
        error {wrong # args: should be "withOpenFile channelIdName filename ?access? ?permissions? script"}
    }

    upvar 1 [lindex $args 0] channelid

    try {
        open {*}[lrange $args 1 end-1]
    } on ok channelid {
        uplevel 1 [lindex $args end]
    } finally {
        catch {chan close $channelid}
    }
}

The point of having a try structure without error handling is that it ensures that the channel will be closed even if the script terminates with an exception.

So, instead of

set filename foo.bar
set mytext {foo bar}
# ...
set f [open $filename a]
chan puts $f $mytext
chan close $f
# ...
set f [open $filename]
set result [chan read $f]
chan close $f

you could write

set filename foo.bar
set mytext {foo bar}
# ...
fileputs $filename $mytext
# ...
set result [slurp $filename]

or

set filename foo.bar
set mytext {foo bar}
# ...
withOpenFile f $filename a {chan puts $f $mytext}
# ...
set result [withOpenFile f $filename {chan read $f}]