Home

tcl-defer

Summary

This is moving to tcllib

This is a re-implementation of "defer" from Go in Tcl. It has similar semantics to "defer" but they are not identical. Additionally, several variations are supported.

The "defer" command is used to defer execution of some code until the current scope ends. That is, until the procedure or lambda returns (normally or due to an exception).

The deferred code may not execute in all cases of the scope terminating, for example if the process is terminated then the deferred code will not be executed. Additionally, if the interpreter is terminated while the procedure is still executing the deferred code will not execute. However, for most things it should be safe to defer them.

Multiple defer statements may be outstanding for a given scope. If there are multiple deferrals, they will be executed in the order of last-in first-out. This is also how "defer" works in Go. This means the most-recently executed defer statement will be executed first when the scope is being cleaned up.


Usage

  1. Simple case:
    package require defer 1
    set fd [open /dev/null]
    defer::defer close $fd
  2. Fake closure:
    package require defer 1
    set fd [open /dev/null]
    defer::autowith { puts "Closing fd=$fd"; close $fd }
  3. Explicit variables to build the closure:
    package require defer 1
    set fd [open /dev/null]
    defer::with fd { puts "Closing fd=$fd"; close $fd }

Discussion

defer::defer

The base case of "defer" is the procedure "defer", this one is actually exported from the namespace so you can import it if you want to use it. It relies on Tcl to evaluate the arguments before invoking the procedure so it is very similar to the Go implementation, you can use [apply] instead of func() to create anonymous functions and they operate pretty much how you would expect.

defer::with

Since the deferred code does eventually run in a lambda anyway the "defer" package offers some convenience functions to simplify this syntax. defer::with accepts a list of variables to resolve immediately and pass the values into the lambda. For example, instead of:

defer::defer apply {{fd} {
      puts "Closing fd=$fd"
      close $fd
}} $fd

You can write:

defer::with fd {
      puts "Closing fd=$fd"
      close $fd
}

Which may be slightly easier to read. The only difference is the level at which the code executes -- the code with "apply" is one level deeper.

defer::autowith

Taking defer::with a little bit further, defer::autowith copies all local variables into the lambda so you don't need to specify them.