Define data-dependent function, various ways

julia> f(x) = x^2
f (generic function with 1 method)

julia> f = y -> y^2
ERROR: invalid redefinition of constant f
Stacktrace:
 [1] top-level scope at REPL[2]:1

julia> g = y -> y^2
#3 (generic function with 1 method)

julia> g = z -> z^2
#5 (generic function with 1 method)

One is a named generic function. The other is an anonymous function bound to a variable. Nothing involved is const, except insofar as one could act like the named generic function is const since it can’t be easily rebound.

The const concern is distinct from the named generic function vs. anonymous function distinction.

4 Likes

I want to chime in here in support of @lmiq. Understanding closure performance is not easy, even for experienced users. There have been various threads on Discourse and Github that discuss performance problems with closures, which is why FastClosures.jl was created. For example, there is this open issue on Github:

I haven’t sat down and studied the scenarios where closures are performant and the scenarios where closures are not performant, so I often wonder if I should be reaching for FastClosures.jl. Is it really just as simple as the following?

Recently there was a question about closures on Stack Overflow. In Bogumil’s answer, he pointed out that two different closures were type unstable. For example, this is type unstable:

counter = let
    local counter = 0          # local variable
    f() = counter += 1         # returned function
end

But I have no idea why that is type unstable.

If nothing else, we have a documentation problem. It would be nice if there was a section of the manual that very clearly spelled out which situations create slow or type unstable closures and which do not.

5 Likes

I think we actually already have that. The case you’ve brought up here is covered in the performance tips here: https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-captured and the issue with global variables is in the performance tips here: https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-global-variables

The reason that the global variables tip doesn’t mention closures in particular is that it applies to all Julia code, not just closures. Any access of a non-const global variable will be slower and harder to type-infer than code which does not use a non-const global variable.

3 Likes

The manual has a bit on callables, with an example.

The manual is not what people are missing – they’re looking for a cookbook that maps specific problems they’re working on to end-to-end solutions.

2 Likes

Yes, I am aware of this. But it is an impossible problem: the space of specific problems is simply too large to even navigate, let alone provide a solution for.

It is much better to invest into learning about building blocks. Examples should help with this, but are not meant to be an exhaustive dictionary of all possible problems.

2 Likes

The fact that the variable to which a function is bound is just a variable is not obvious in general. These things are not common in some languages (Fortran, for example). This is the main origin of my confusions.

Concerning additional documentation: I more or less go with the ones that think this is impossible. Essentially I think that these threads are that. For instance, in Python, if one searches for a solution to a problem, it is almost certain that we reach a stack overflow or other discussion, and not any official Python documentation. The documentation of Julia is pretty fantastic in my opinion and, while it can be improved in some specific points, the fact is that it will not be the most common place in which a search will end as the language becomes more popular and specific problems and use cases become covered by questions and answers here.

That said, I suggested in some other thread that if we had an infrastructure within discourse to produce didatic material and develop programming curses, that material and the questions and answers that each teaching effort produces would end up collaborating for the production of this example-based docs. For example, I am giving a programming course now (in Julia, for chemists that mostly do not have much programming experience), and I am producing content, like this (unfortunately in Portuguese):

https://github.com/m3g/CKP/blob/master/disciplina/codes/Int-vs-Any.md

and this: https://github.com/m3g/CKP/blob/master/disciplina/simulacoes2.pdf

Much of the topics I end up writing with detail come from the fact that some students report some issues, some of them which I can answer directly, some of them that actually lead me to understand better the language, many times after coming here to ask for help. Yet, this material will probably not be very visible to someone outside the course. If it was part of discourse in some way, with possible eventual inputs of other people, or stimulating discussions among the students of the course themselves, that could have another impact.

2 Likes

Oh yeah, I don’t mean to suggest that it should be obvious. It’s just hard to know exactly where to make that particular point so that users who need to learn it will be receptive to it while users who do not will not be distracted by it. For example, precisely the same notion applies to types which likewise cannot be bound to variables in many languages.

There is an unusual feature of Julia here, where struct Foo or function foo are slightly more special than Foo = ... or foo = ... in that the former create constants while the latter do not. Maybe that’s the fundamental point we need to make more clear?

5 Likes

Clearly that is something that was not completely clear to me. Now, I guess that is written in the manual somewhere. I am not sure if we can guarantee that, being written anywhere else, it will get read specifically by someone that is facing the same confusion. The confusions have the property of taking us to the wrong places of a user manual as well. We quickly find what we are searching for in a manual when we almost know exactly what we want.

1 Like

Thanks for the link to the “Performance of captured variables” section. I read that a while ago, but it looks like that section has been expanded a lot since I read it, so I will take another look.

Part of the point I’m making is that performance of closures can be subtle, and comments from experts like “it’s not tricky at all” are off-putting and not very helpful.

Just wanted to quickly add that the main issue here is the fact that each invocation of x->f_closure(x, data) creates a new function.

f = x->f_closure(x,data)
g = x->f_closure(x,data)
@show f == g # false

When you call s_outer(x->f_closure(x,data), x0) multiple times, it needs to compile each time because the inputs are different functions. You can, however, circumvent this problem by assigning your anonymous function to a variable.

func_test = x -> f_closure(x, data)
@time s_outer(func_test,x0)

    0.002346 seconds (984 allocations: 65.018 KiB)

@time s_outer(func_test,x0)

    0.000002 seconds (1 allocation: 16 bytes)
1 Like

It is best to create these kind of closures within functions, not at the REPL, then AFAIK they won’t need to be compiled again (for the same types).

3 Likes

Agreed that these closures should be defined within functions to avoid precisely these issues. I just point out out the initial comparison was probably not doing what he intended to do because of the issue I raised. (I for one was confused why closure could not get compiled while f_global_const_data could be compiled just fine).

2 Likes

While searching for an introduction to closures, I came across this clear explanation in Julia Language Pedia (Introduction to Closures). No clue on how that “Juliapedia” was put together, the content is excellent but unfortunately it seems to have stopped being updated around 2017? They have a disclaimer saying the material is an extract of the original Stack Overflow Documentation… If there was a way to further build upon all of Julia’s knowledge and solutions already available in discourse, that would be of great service.

1 Like

I think that closures in particular should have a short explanation in the manual, which uses the term in various places.

Julia’s closures are not conceptually different from closures in other languages that have them, so those who have encountered the concept do not need a Julia-specific introduction, but for the rest of the readers a paragraph with an example and then an explanation how they related to function-like objects could be helpful.

5 Likes