What is the difference between a variable that contains a function an a function?

Why is there a difference between

f = x->5

and

f(x) = 5

Why does the compiler treat these differently?
Since if you run both of the above you get

ERROR: cannot define function f; it already has a value

But they both behave the same, as in:

julia> f(1)
5

Maybe I’m asking why should these two be treated differently?

1 Like

The assignment-form syntax f(x) = 5 and the equivalent function ... end syntax both implicitly declare f a const name in the definition’s global scope. This also occurs for struct definitions. A const name means that reassignment is undefined behavior or an error. Conversely, a non-const name cannot be made into a const name afterward; this threw the error you saw. Note that adding methods to an existing function or type like f() = 0 or replacing a method f(x) = 7, is not reassigning f. You’d need actual assignment syntax f = ... to try that.

The compiler can do some great optimizations when accessing const names; they’re actually necessary to let function calls and type instantiations be optimized at all. For example, if you defined f = x->5 and g(x) = f(x), g cannot assume f will be the same function and cannot be optimized much. You can get around this with some higher order functions like g(foo::Function, x) = foo(x), but you probably prefer to access global names rather than pass every function and type through the arguments like g(f, x).

6 Likes

If you write

julia> f = x->5
#3 (generic function with 1 method)

julia> g(x) = 5
g (generic function with 1 method)

julia> supertypes(typeof(f))
(var"#3#4", Function, Any)

julia> supertypes(typeof(g))
(typeof(g), Function, Any)

Indeed they are both functions. The difference is, the name g is now assigned to the function, whereas f is just a variable which currently stores the address to the anonymous function “#3” (you couldn’t call it with #3(2)` though).

julia> g = 2
ERROR: invalid redefinition of constant Main.g
Stacktrace:
 [1] top-level scope
   @ REPL[16]:1

julia> f = 2
2

I hope this clears it up for you! You can also take a look at the docs about this.

On a side-note, this seems to induce performance issues:

julia> using BenchmarkTools

julia> @btime f(2)
  8.603 ns (0 allocations: 0 bytes)
5

julia> @btime g(2)
  1.060 ns (0 allocations: 0 bytes)
5

Not sure why exactly this is the case, probably because g is a constant.

4 Likes

Try @btime $f(2)? BenchmarkTools’ $-interpolation is needed for non-const globals generally.

1 Like

Got it! I forgot f was indeed a non-const variable, even though I said it before…

julia> @btime $f(2)
  1.059 ns (0 allocations: 0 bytes)
5