Closures section in documentation is not clear enough

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.

11 Likes