Closures
A lambda that needs a variable from the code around it lists the variable in
square brackets after sub or function. That captures it, and the lambda
becomes a closure: the code plus the captured variables. Every capture
states how it is held:
'' A lambda that uses variables from around it lists them in square
'' brackets, and says for each one how it is held.
dim as int32 calls = 0
dim as int32 step_ = 5
'' byref: the lambda works on the caller's variable itself.
var countCall = sub[ byref calls ]( )
calls += 1
end sub
countCall( )
countCall( )
print "calls:"; calls
'' byval: the lambda keeps its own copy, taken when the lambda is made.
var addStep = function[ byval step_ ]( byval x as int32 ) as int32
return x + step_
end function
var addStepNow = function[ byref step_ ]( byval x as int32 ) as int32
return x + step_
end function
step_ = 1000
print "byval copy:"; addStep( 1 ) '' still adds 5
print "byref view:"; addStepNow( 1 ) '' adds the current 1000
'' Modes can be mixed in one list. step_ is 1000 by now, and that is
'' the value this lambda copies.
dim as int32 total = 0
var addScaled = sub[ byref total, byval step_ ]( byval x as int32 )
total += x * step_
end sub
addScaled( 2 )
addScaled( 3 )
print "total:"; total
Output
calls: 2
byval copy: 6
byref view: 1001
total: 5000
| Capture | The closure holds | Writes inside the lambda |
|---|---|---|
byref calls | the caller's variable | reach the caller |
byval step_ | its own copy, taken when the lambda expression runs | stay in the copy |
countCall increments the caller's calls. addStep copied step_ when its
var line ran, while it was 5, so it still adds 5 after step_ becomes 1000;
addStepNow holds the variable and sees 1000. addScaled mixes the two modes
and copies step_ at the point it is made, by which time it is 1000.
A byval capture is a snapshot taken when the lambda is made, not when it
is called. A closure written early and called late, such as a handler or a
comparer kept for later, sees the value from when it was written. Capture
byref when the lambda must see the variable as it is at each call.
There is no default mode. A capture without byval or byref is refused:
'' Every capture must say byval or byref. Does not compile.
dim as int32 count = 0
var bump = sub[ count ]( )
count += 1
end sub
bump( )
capturemode.tiko(3,17): error TKO0413: every lambda capture needs an explicit 'byval' or 'byref'
A closure is not a procedure pointer, because a pointer has no room for the
captured variables. Assigning one to a procedure-pointer type is
TKO0738: no conversion between these two types. To pass a closure to a
procedure, the parameter is generic, as Higher-order procedures
shows.
Activities
- Write a closure that appends each word it is given to a
ustringcapturedbyref, and call it three times. - Change
addStepto capturebyrefand predict its output before running.