So, you have a function that returns a function.
mkadder(x) = y -> x + y
add3 = mkadder(3)
add3(2) == 5
# true
add4 = mkadder(4)
add4(5) == 9
# true
# add3 still works the same
add3(8) == 11
# true
add3 and add4 are closures. They are functions that carry along the values in the scope where they were created.
This is a good alternative to using globals. For example, one can speed up the slow recursive fibonacci calculation this way, by caching results between calls, but without having to rely on global state.
function fib(num::Real)
cache = typeof(num)[1, 1]
function inner(n)
if length(cache) >= n
return cache[n]
else
push!(cache, inner(n-1) + inner(n-2))
return cache[n]
end
end
inner(num)
end
for i in 1:10:50
println(fib(i))
end
output:
1
89
10946
1346269
165580141
Caching increases the speed of naive recursive fibonacci from O(2^n) to O(n). State is bad, but it helps sometimes. Closures help keep the state private. Closures can also be used as an alternative way to implement lazy evaluation.
function counterto(n)
count = 0
return () -> (count >= n ? nothing : count += 1)
end
tencount = counterto(10)
while (n = tencount()) != nothing
println(n)
end
output:
1
2
3
4
5
6
7
8
9
10
In this example, you use a closure that will return the next value every time you call it, which is useful for creating a qick-and-dirty iterator pattern (generating values as needed, rather than all at once. Saves memory). In Julia, you’d normally implement an iterate
method, but that requires creating a special struct. The other way people sometimes do this in Julia is with Channels. I’m not sure which is preferable, though the Channel pattern does look a little cleaner to my eye.