Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tcl: idiomatic [closure]

Tags:

closures

tcl

Tcl has apply and lambda, but no closure.

As of tcl 8.6, what is idiomatic form or closure?

Published patterns appear confusing, as is also one below.

Example:

#!/usr/bin/env tclsh
::oo::class create Main {
    method ensurePath {url args} {
        # closure definition, takes time to recognize
        set performPath [list my performPath $url {*}$args] 
        if {0} {
            # closure application, can reduce needless noise?
            {*}$performPath alpha beta
        } elseif {1} {
            {*}$performPath omega gamma
        } else {
            # no performPath
        }
    }
    method performPath {url args} {
        puts "[self class]::[self method] {$args}"
    }
}
set main [Main new]
$main ensurePath url one two

Output:

::Main::performPath {one two omega gamma}
like image 224
Andrei Pozolotin Avatar asked Nov 20 '25 15:11

Andrei Pozolotin


1 Answers

Tcl doesn't do full closures, but it can do limited versions of them for key use cases; if you see {*} applied to the apparent first word of a command, that's the sort of thing that is going on. For example, you were doing the (object) callback use case. That's pretty simple to do:

set performPath [namespace code [list my performPath $url {*}$args]]

(The namespace code ensures that the callback will be evaluated in the correct namespace, even if run from outside the object.)

We could even make that neater by defining a helper procedure:

proc ::oo::Helpers::callback {method args} {
    tailcall namespace code [list my $method {*}$args]
}
set performPath [callback performPath $url {*}$args]

Similarly, the variable capture use case can also be done. Here's the simplest version that assumes that all variables are not arrays:

proc closure {body} {
    set binding {}
    foreach v [uplevel 1 info locals] {
        upvar 1 $v var
        if {[info exists var]} {
            lappend binding [list $v $var]
        }
    }
    return [list apply [list $binding $body [uplevel 1 namespace current]]]
}

Demonstrating how to use it:

proc foo {n} {
    set result {}
    for {set i 1} {$i <= $n} {incr i} {
        lappend result [closure {
            puts "This is $i of $n"
        }]
    }
    return $result
}
foreach c [lreverse [foo 10]] {
    {*}$c
}

(Handling arrays and arguments makes this rather more complicated.)

If you need modifiable state in the “closure” then you need to either use an object or a coroutine to hold the state. The main issue with either of them is that you need to explicitly clean up the resulting command when you're done; standard Tcl doesn't garbage collect unused commands.

like image 197
Donal Fellows Avatar answered Nov 22 '25 04:11

Donal Fellows



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!