Reputation: 937
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}
Upvotes: 3
Views: 536
Reputation: 13282
AFAIK, there is no idiomatic way to specify closures in Tcl. While the things that closures do can be emulated in Tcl, the core concept is orthogonal to Tcl.
The classical adder closure example (from Wikipedia)
function add(x)
function addX(y)
return y + x
return addX
variable add1 = add(1)
variable add5 = add(5)
assert add1(3) = 4
assert add5(3) = 8
can be written in Tcl like
proc add {name x} {
interp alias {} $name {} ::tcl::mathop::+ $x
}
add add1 1
add add5 5
add1 3
# => 4
add5 3
# => 8
This "closure" doesn't have mutable state, but that can be fixed. This is (one way to do*) Paul Graham's "accumulator generator":
proc foo {name n} {
set int [interp create]
$int eval set n $n
$int eval {proc _foo i {incr ::n $i}}
interp alias {} $name $int _foo
}
But that one doesn't allow shared state... and so on.
I think the best way to deal with closures in Tcl is to determine why a closure would be useful, and see if there isn't a Tcl idiom for doing that. Closures as such isn't really a Tcl thing.
*) here is another, I don't know if this one should be considered better.
proc _foo i {
set n $i
while 1 {
incr n [yield $n]
}
}
proc foo {name n} {
coroutine $name _foo $n
}
Upvotes: 3
Reputation: 137737
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.
Upvotes: 3