bindlife

Binding a procedure's life to a variable's life

SS 2004-04-18: Variable unset traces allow to bind a procedure's lifetime to the lifetime of a variable, so that once a variable is unset, the procedure is removed. This can have useful applications, I wrote it for a real-life programming need, to create a procedure that returned other procedures crated on-the-fly, able to perform specific tasks on local variables. It was boring and error prone to destroy the created procedures by hand when they were no longer useful (usually when the variable was near to go out-of-scope), so I wrote the bindlife procedure.

Another lambda implementation

Extending the concept a little bit, it was possible to bind a procedure's lifetime to the lifetime of the procedure invocation that created it. It's just like local variables, when the procedure call that crated the local variable returns, the variable go away. What about to do it even for procedures? This allows to implement a version of lambda that is garbage collected in a better way than the usual implementation of lambda using info level to create the name. With this implementation it's possible to create more lambdas in the same procedure and to store they in different variables, to pass they to other procedures, and so on. What's *not* possible, is to return the lambdas because they go out of scope once the procedure that created the lambda returns.

So this lambas are actually "half" first-class, can be passed to functions but not returned. The price to pay for this form of automatic memory management.

Rules for safe usage

You should follow this rules to avoid problems with anonymous functions returned by this lambda implementation:

  • lambdas should not be returned by procedures (unless they got lambdas from arguments).
  • lambdas should not be stored in global variables.
  • Remember that this lambdas are destroyed when the creating procedure exits, so to create 1000000 anonymous functions in a for loop and assign they to the same variable will waste all the memory.

Btw, for my usual programming, this version of lambda is comfortable enough because it's not slow as functions as values (otherwise perfect, actually I wish this supported by Tcl!), nor limited and badly collected as the common version using info level as name, or the "very telling name" of the lambda's body itself.

# bindlife.tcl -- Binds lifetime of procedures to variables lifetime.
# Copyright (C) 2004 Salvatore Sanfilippo <antirez (at) invece (dot) org>
#
# Under the same license of Tcl 8.4

namespace eval bindlife {}
namespace eval lambda {}

set bindlife::destrId 0
set lambda::lambdaId 0

# Bind a procedure's life to a variable. Once the variable is
# unset, the procedure will be removed.
proc bindlife::bindlife {procname varname} {
    # Get the fully qualified name of the proc
    set ns [uplevel [list namespace current]]
    # If the proc call did not happen at the global context and it did not
    # have an absolute namespace qualifier, we have to prepend the current
    # namespace to the command name
    if { ![string equal $ns "::"] } {
        if { ![string match "::*" $procname] } {
            set procname "${ns}::${procname}"
        }
    }
    if { ![string match "::*" $procname] } {
        set procname "::$procname"
    }
    # Create the trace handler procedure
    set destructor ::bindlife::bindlifeDestructor[incr ::bindlife::destrId]
    proc $destructor {name1 name2 op} [format {
        rename %s {}
        rename %s {}
    } [list $procname] $destructor]
    uplevel [list trace add variable $varname unset $destructor]
    return {}
}

# Implements a version of [lambda] that is collected once the creating
# procedure returns, in a similar manner to local variables.
proc lambda::lambda {arglist body} {
    upvar __bindlife__sentinel__variable__ sentinel
    set lname ::lambda::lambda[incr ::lambda::lambdaId]
    proc $lname $arglist $body
    set sentinel {}
    uplevel [list ::bindlife::bindlife $lname __bindlife__sentinel__variable__]
    return $lname
}

# Example
proc foo {} {
    set a [lambda::lambda x {expr $x*$x}]
    set b [lambda::lambda x {expr $x+$x}]
    puts [$a 5]
    puts [$b 5]
}

foo