Closures section in documentation is not clear enough

In the closures section there is only one example that is not clear enought to understand how to use closures in Julia.
Can you provide some explaination and also an example about the correct way to make pseudo-objects using clousures in Jula?

2 Likes

That section just describes closures in Julia, not the concept of closures in general and how to use them.

Perhaps you could explain what you are trying to do for more specific help.

1 Like

Perhaps this example will help you. To make closures, I typically use a template that looks kind of like this

const binomsum = ( () -> begin
        Y = Array{Int,1}[Int[1]]
        return (n::Int,i::Int) -> (begin
                for k ∈ length(Y)+1:n
                    push!(Y,cumsum([binomial(k,q) for q ∈ 0:k]))
                end
                i ≠ 0 ? Y[n][i] : 0
            end)
end)()

where Y[n][i] is my cache of computed values, which I can access with binomsum(n::Int, i::Int). If the cache does not contain the values yet, it will automatically fill the cache up to that value.

1 Like

I don’t see anything wrong with your example.

If you’d like to relate it back to the example in the documentation you could rewrite your example as

function adder(Y)
    return (n::Int,i::Int) -> (begin
            for k ∈ length(Y)+1:n
                push!(Y,cumsum([binomial(k,q) for q ∈ 0:k]))
            end
            i ≠ 0 ? Y[n][i] : 0
        end)
end

const binomsum = adder(Array{Int,1}[Int[1]])

Because the captured variable value is always the same, your example could also have been written as

function adder()
    Y = Array{Int,1}[Int[1]]
    return (n::Int,i::Int) -> (begin ... end)
end

const binomsum = adder()

And because you create const binomsum only once, you don’t need to name the outer function ‘adder’.
An anonymous function is sufficient. The final result would be the same as your example.

I don’t need the closures general concept but only a good way to make pseudo-object with closures in Julia, an example of one psudo-object with some methods and some attributes. Thanks

What is a pseudo-object?

1 Like

This is an example with JS

But there are a lot way to do it in JS and I think also in Julia, but the link show a good way to do it in JS, I’m looking for a good transaltion of that code in Julia

That link does not have a single use of “pseudo-object” and it contains a few different JS patterns so I’m still not sure which one exactly you are talking about.

FWIW, the problems (and solutions) mentioned in that post is very js specific (this is dynamically scoped) so there’s pretty much no (need for a) solution to a non-existing problem.

Also, most the ingredients used, (defining contructor, closure that captures local variable, calling constructor) are all well defined and documented syntax in julia so you need to be clear which one you are asking about (you doesn’t seem to be looking for a closure only example). Also, not all code are translatable due to julia not having a dynamically scoped this and, again, requires better clarification of your actual question.

Also note that julia objects are not just a different spelling of Dict with String as keys (as is the case in js) and all fields in julia are typed. These can affect the usefulness of some of the code in that post in a subtle way (e.g. the problem may not exist, the solution is not good practice, or a mixed of these with possibly other issues).

P.S. I thought the canonical way to define a OOP object is via prototype…

4 Likes

Julia’s object model is very different than JavaScript’s. Methods are defined on the type signatures of functions, not objects (as in this strange article) nor on prototypes (as in normal JavaScript).

  • Julia methods are never bound to instances.
  • Any concrete objects available in a Julia function are sent as arguments or defined in an outer scope.
  • In JavaScript, new objects are derived from other objects (prototypes). In Julia objects are instances of structs (or primatives).
  • In JavaScript, a new prototype can be defined at any point during execution, in any context. In Julia, structs can only be defined at the top-level scope and are analyzed at compile-time.

If you are determined to use this pattern in Julia, you could do something like this:

function badobject(foo, bar, baz)
    Dict(
        "addtofoo" => function(x)
            foo + x
        end,
        "subtractfrombar" => function(x)
            bar - x
        end
    )
end

However, this is not good Julia! Define methods for your structs as regular functions and pass objects to them as needed. Just use closures for the normal stuff: currying and/or allowing a single function to keep private state between calls.

Using a dictionary like this will be much slower than using structs and functions. Part of the reason Julia is so fast is that its object system allows much more efficient data allocation. Most structs can be compiled to contiguous data blocks in memory. Emulating JavaScript patterns with dictionaries in Julia will make your code much slower. In fact, it will probably be even slower than the equivalent JavaScript, since the Julia JIT is targeted at making the most of statically analyzable structs, and JavaScript runtimes have to be more adapted to the ubiquity of lookup tables.

3 Likes

I don’t think I can help you without some context, and a description of what you are trying to achieve, instead of an example of what someone does in JS, which is a completely different language.

Encapsulating state with closures is a cute exercise, and you can also do that in Julia, but in practice it is not necessary.

This explains so much! Closures come up a bunch in discussions here, and I’ve read that bit of the docs, but still have no idea what they are or what they’re for. Anyone know of any good resources that explains the concept of closures in general and how to use them? :laughing:

3 Likes

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

Heh, yeah - you could have done lmgtfy too. I actually did read that before - it wasn’t a ton of help. Wikipedia articles are great if you understand like 85% if the jargon, but I’ve never taken a cs class and there are some pretty glaring gap in my understanding. I do what I can with Wikipedia and nested stack overflow searches. I actually learned a ton from the Julia manual and googling the things in there I didn’t understand, though closures were still a black box until the past below yours.

I wish I had more hearts to give! Super clear now, and I’m bookmarking for later review. Thanks! :heart::heart::heart:

Sounds like you are complaining about me posting a link to wikipedia because you find it hard to read. I actually thought the wikipedia article had a nice existing explanation, so I figured it’s worth noting.

1 Like

Closures are amazingly powerful and can be used to implement a variety of useful constructs, including objects that encapsulate and continuation-like constructs. Demoing this is part of every self-respecting book on Lisp dialects.

While all of these things are possible with closures in Julia, too, in most cases it is more idiomatic to use a first-class representation, eg a composite type, which you can make it callable. It pretty much amounts to the same compiled code (in the ideal case), but makes debugging, documentation, and extensions easier.

The most important practical application of closures is higher-order programming, ie using functions as arguments. Eg

julia> filter(x -> x % 3 == 0, 1:10)
3-element Array{Int64,1}:
 3
 6
 9
3 Likes

Not complaining :grinning:. No hard feelings - just didn’t want to leave the impression that I hadn’t put in some minimal effort before posting asking for resources.

Neat - I actually do this all the time with filter and map especially, didn’t realize it was related.