`foo = () -> 3` vs. `foo() = 3`

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)

julia> foo()
3

julia> bar()
3

help?> foo
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":
  [1] (::var"#1#2")() in Main at REPL[1]:1

help?> bar
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":
  [1] bar() in Main at REPL[2]:1

The only difference is that (::var"#1#2") appears in the help description where I would expect to see foo.

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

function f1(x)
    if x == 1
        g() = 3
    else
        g() = 4
    end

    return g
end

function f2(x)
    if x == 1
        g = () -> 3
    else
        g = () -> 4
    end

    return g
end

julia> f1(1)()
4

julia> f2(1)()
3

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.

2 Likes

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.

3 Likes

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: disallow methods of local functions in different blocks · Issue #15602 · JuliaLang/julia · GitHub

I think in an issue it is explained better than I can do.

2 Likes

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 x, f1(x)() will call g(), which will always return 4.

1 Like

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 @everywhere

foo() = 3
@fetch foo() # error

@everywhere foo() = 3
@fetch foo() # works
1 Like

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.