I saw the following line in one of the Flux.jl examples
evalcb = () -> @show loss(tx, ty)
In my mind, this is identical to
evalcb() = @show loss(tx, ty)
which I find more legible.
An even more minimal example:
julia> foo = () -> 3
#1 (generic function with 1 method)
julia> bar() = 3
bar (generic function with 1 method)
search: foo floor pointer_from_objref OverflowError RoundFromZero unsafe_copyto! functionloc StackOverflowError
No documentation found.
foo is a Function.
# 1 method for anonymous function "#1":
 (::var"#1#2")() in Main at REPL:1
search: bar baremodule SubArray GlobalRef clipboard BitArray backtrace BitMatrix catch_backtrace AbstractRange
No documentation found.
bar is a Function.
# 1 method for generic function "bar":
 bar() in Main at REPL:1
The only difference is that
(::var"#1#2") appears in the help description where I would expect to see
What is the purpose of a construction like
evalcb = () -> @show loss(tx, ty)?
They are different objects with different behaviour in regards to optimization, scope rules and so on.
Compare these two definitions
if x == 1
g() = 3
g() = 4
if x == 1
g = () -> 3
g = () -> 4
In the first function
g is defined only once (probably you may say it is “global” to the scope of the function). But in the second function these are two different and independent objects.
It probably does not explain why in this particular example of the Flux.jl the second form was chosen, but probably due to the nature of the operations that happen in Flux.jl it is safer to use it always, so you wouldn’t run into some unexpected corner cases.
I can only think of one difference that isn’t a clear advantage to the non-anonymous version:
foo = () -> bar()
foo = 3 # legal
foo() = bar()
foo = 3 # illegal
Hardly anything I would consider that beneficial though.
Could you explain a bit more about this?
I wonder, why it is the second
g()=4 which is the only once definition?
Well, it turns out that this is more of a bug than feature.
Discourse discussion: Confused by behaviour of nested functions
And related github issue: https://github.com/JuliaLang/julia/issues/15602
I think in an issue it is explained better than I can do.
I think it’s because Julia evaluates
g() = 3 and then overrides it by evaluating
g() = 4 (even if the condition is not hit). So regardless of the value of
f1(x)() will call
g(), which will always return
Glad I’m not the only one baffled by the behavior of your
f1. I have gotten away with the assumption that if an
if condition is met, then the material inside of an
else condition will never have any effect.
Just for completeness, another small difference is how they’re serialized. For the anonymous version, the function definition is actually serialized, where as the non-anonymous one just the name is serialized. The niche place where this might come up is when using Distributed’s auto-global shipping, so e.g. this works,
foo = () -> 3
@fetch foo() # foo definition shipped to worker, run there, answer returned
whereas the non-anonymous version you need to have
foo actually defined on all workers (since only its name is serialized), which ammounts to an
foo() = 3
@fetch foo() # error
@everywhere foo() = 3
@fetch foo() # works
I think one should not do that without reason. It declares a global variable and there will be an overhead in calling it in respect to a declared function.
If one uses anonymous functions as intended:
The primary use for anonymous functions is passing them to functions which take other functions as arguments.
… there is no such overhead.
Maybe in an example this is not so important. It doesn’t seem to me that the context demands it.
That definition of
evalcb is itself wrapped inside a function. Is it still correct to say it declares a global variable in this case?
you are right. Inside a function it is not a global variable and it doesn’t make a difference. I was confused.