# Again on closures and type instability

Consider the simple functions:

``````inner(x,a) = a*x
outer(f,x) = f(x)
``````

If one calls `outer` using a closure and global, non-constant parameter `a` , we have a type-instability:

``````julia> a = 5; x = 2;

julia> @code_warntype outer( x -> inner(x,a), x)
Variables
#self#::Core.Compiler.Const(outer, false)
f::Core.Compiler.Const(var"#5#6"(), false)
x::Int64

Body::Any
1 ─ %1 = (f)(x)::Any
└──      return %1

``````

This is solved by declaring `a` constant or wrapping the call to `outer` into a function which, by itself, receives `a` as a parameter, i. e. `f(x,a) = outer(x->inner(x,a),x))`.

Yet, from a non-specialist point of view, these alternatives should not be necessary if the parsing of `outer(x->inner(x,a),x)` translated directly to that, or to something like:

``````function outer2(a,x)
clos = x -> inner(a,x)
outer(clos,x)
end
``````

That seems to be possible, since the anonymous function defined as a parameter cannot change, that is, it seems to be as constant as any other data passed to the `outer` function which are constant from the point of view of the function even if they are non-constant in the global scope. The function then would specialize to the input parameters of `inner` as well.

Is there a fundamental reason for that not being possible (or simple?)

The `x -> inner(x,a)` get evaluated (in the scope containing `f`) before it gets passed into `outer`. That is always the case, e.g.:

``````julia> a,b = 1,2
(1, 2)

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

julia> f(a + b)
3
``````

there `a+b` gets evaluated first and then it’s passed to `f`. The same occurs in the case of the anonymous function and thus it binds a non-const global. This cannot be changed willy-nilly.

4 Likes

Any idea if that is because of a technical difficulty, or if that behavior is desirable for some reason, or if it is because it would brake anything?

Edit: Now I see that your example, with two parameters, indicated that changing that would brake a lot of things… A very specific parsing of the arguments and of the closure would be needed.

Edit2: My “solution” button disappeared

I think in certain categories there are no “solutions”. Not sure though.

1 Like

I am writing some of the things I learn here in this docs and sharing with some students, and I have written a section on closures now, incorporating what I have learnt here:

The section on closures of the manual (https://docs.julialang.org/en/v1/devdocs/functions/#Closures) is very succinct, so this may be useful for others. If someone happens to read that and finds errors, please let me know.

2 Likes

There’s nothing really particular to closures here. In your example `a` is a global. Don’t use non-constant globals if you care about performance.

``````julia> a = 5; x = 2;
julia> @code_warntype outer( x -> inner(x,a), x)
``````

If you do this in a local scope, all is well:

``````julia> inner(x,a) = a*x
inner (generic function with 1 method)

julia> outer(f,x) = f(x)
outer (generic function with 1 method)

julia> function doit()
a = 5; x = 2;
return outer( x -> inner(x,a), x)
end
doit (generic function with 1 method)

julia> doit()
10

julia> @code_warntype doit()
Variables
#self#::Core.Compiler.Const(doit, false)
#1::var"#1#2"{Int64}
a::Int64
x::Int64

Body::Int64
``````

(edit: I also just enabled solutions on this category)

4 Likes

It was not completely clear to me the scope in which the closure, inside a function call, could be evaluated. The previous answer made that clear to me.