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}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With