[Peter Lewerin] 2014-01-11: ''with open file'' is an old programming pattern or idiom that abstracts the following: 1. open a channel to a file 1. do something with the channel 1. close the channel (When I started this page, I first searched this wiki for similar pages, but still missed this page: [Playing with with] which addresses 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] set txt [read $f] close $f ====== This is tedious, verbose, over-specific, and error-prone. I can shave off a couple of characters while allowing the operation on channel to be specified at invocation, adding basic error handling, ensuring that the file will be closed, and making it a single-invocation one-liner. In fact, I will show ''two'' ways of doing that. ***First variant*** The idiom has been turned into a language element by Common [Lisp], specifically as a Lisp macro, `with-open-file` (see [http://www.lispworks.com/documentation/HyperSpec/Body/m_w_open.htm#with-open-file%|%CLHS%|%]). A Tcl command along the lines of (NB not quite the functional equivalent of) the CL macro might look like this: ====== proc with-open-file {__varName __script args} { try { open {*}$args } on ok $__varName { try { eval $__script } on ok __scriptResult { # do nothing, just get scriptResult } on error {__result __options} { # do nothing, just collect result and options } } on error {__result __options} { # do nothing, just collect result and options } finally { # before we leave, close the file (if it was ever opened) if {[info exists $__varName]} { close [set $__varName] } } if {[info exists __options]} { # we have an exception, rethrow it return -options $__options $__result } else { return $__scriptResult } } ====== Usage (the file `foobar.txt` does not exist): ====== % with-open-file f { read $f } foobar.txt ##! raises exception 'couldn't open "foobar.txt": no such file or directory' % with-open-file f { puts $f foo } foobar.txt w % with-open-file f { read $f } foobar.txt foo ====== This command takes as arguments a ''variable name'', a ''script'' (the variable name can be dereferenced to a channel identifier when evaluating the script), and a ''list of arguments'' to be passed to the `[open]` command. The `with-open-file` guards against exceptions both when opening the file and when evaluating the ''script'', and makes sure that the channel is closed even if an exception is raised. On return, the command either rethrows any exceptions raised or returns the result of evaluating the ''script''. I've used the non-standard `__xxx` form for the local variables to, if possible, avoid collisions with whatever the caller decides to call the variable that will hold the channel id. To be really sure there are no collisions, consider implementing `getGloballyUniqueName`. The example at the top of the page can now be written as: ====== set txt [with-open-file f { read $f } $myfile] ====== ***Second variant*** I'm not quite satisfied with the above implementation. There's the name collision issue, and there's also the fact that the caller can't tell the command what to do in case of an exception. Hence this slightly different variant: ====== proc withOpenFile {func errfunc args} { try { open {*}$args } on ok f { try { apply $func $f } on ok scriptResult { # do nothing, just get scriptResult } on error {result options} { # do nothing, just collect result and options } } on error {result options} { # do nothing, just collect result and options } finally { # before we leave, close the file (if it was ever opened) if {[info exists f]} { close $f } } if {[info exists options]} { # we have an exception, try calling errfunc if {$errfunc ne {}} { apply $errfunc $result $options } else { # no errfunc lambda to call, rethrow the exception return -options $options $result } } else { return $scriptResult } } ====== Usage (the file `foobar.txt` does not exist, no error function): ====== % withOpenFile {f {read $f}} {} foobar.txt ##! raises error 'couldn't open "foobar.txt": no such file or directory' % withOpenFile {f {puts $f foo}} {} foobar.txt w % withOpenFile {f {read $f}} {} foobar.txt foo ====== With error handling (consider the file `foobar.txt` to be non-existing): ====== % withOpenFile {f {read $f}} {{result -} { puts "Oh dear, $result" }} foobar.txt ##- prints the text 'Oh dear, couldn't open "foobar.txt": no such file or directory' ====== This variant takes two [lambda] arguments (anonymous functions), ''func'' and ''errfunc'' to be called using the `[apply]` command (the second argument is allowed to be a (usually invalid) "null lambda" with the value `{}`) and a ''list of arguments'' to be passed to `open` as in the first variant. If the command manages to open the file, it calls ''func'' and passes the channel id to it as an argument. If anything goes wrong when opening the file or while evaluating ''func'', the command checks to see if ''errfunc'' is a lambda (or at least not an empty string), and either calls ''errfunc'' with the exception data as arguments or just rethrows the exception. In either case, the file is closed before exiting the command. The ''errfunc'' in this example doesn't use its second parameter (the return options dictionary), so I use the variable name `-` for it to make it more anonymous; it's just a personal convention. Note that if you need to use any variables from the script level from where `withOpenFile` is called inside either of the lambdas, you need to use `upvar 2 foo foo` since the call to `withOpenFile` adds one level and `apply` calling the lambda adds one more. Maybe I should change that by `[uplevel]`ing the `apply`? Hmm. The example at the top of the page can now be written as: ====== set txt [withOpenFile {f {read $f}} {} $myfile] ====== or like this: ====== set func {f { read $f }} set errfunc {{r o} { puts "The message is '$r' and the error code is {[dict get $o -errorcode]}." }} set txt [withOpenFile $func $errfunc $myfile] ====== <>Category Command | Category Control Structure | Category Concept