ITA
ITA

Reputation: 3850

Julia function returning anonymous function

I am trying to understand a relatively simple piece of code, but I am not quite able to reason what is happening (I am a Julia newbie coming from Python/Matlab background).

function myfunc(number::Integer)
    double() = 2*number
    square() = number^2
    return _ -> (number, double, square)
end

I understand myfunc is returning an anonymous function that does not care for the value passed to it. So these cases make sense to me:

julia> n4 = myfunc(4)
#9 (generic function with 1 method)

julia> n4(50)
(4, var"#double#10"{Int64}(4), var"#square#11"{Int64}(4))

In the first line n4 refers to the anonymous function itself, whereas in the second the anonymous function is called with parameter 50 and does what it is supposed to: discards 50 and returns the tuple containing data it was defined with.

What I don't understand is how I am able to do:

julia> n4.square
(::var"#square#11"{Int64}) (generic function with 1 method)

julia> n4.square()
16

The fact that n4 which refers to an anonymous function has child objects n4.number, n4.double, n4.square is a surprise to me. How is n4 behaving as if it were a struct? Doing n4(*)[2]() to get back 8 as answer makes sense but when fieldnames(n4) fails something is happening behind the scenes that I don't understand to make n4.double() work. Where/what is the mechanism by which I am able to use . after n4 to get at the functions/data?

Upvotes: 8

Views: 2191

Answers (1)

Jakob Nissen
Jakob Nissen

Reputation: 1991

In Julia, all generic functions (that is, all regular Julia-defined functions) are structs. Any struct can be made callable in Julia, so by default, a normal function is just a zero-field struct (a singleton) made callable. In other words, doing

foo(x) = x+1

is similar to

struct Foo end
const foo = Foo()
(::Foo)(x) = x + 1

This works great for normal functions, but for anonymous functions, this can cause the creation of a large number of new types. For example, you could do: functions = [x -> x+i for i in 1:1000].

Instead of creating 1000 new types, each with a new value of i, Julia here creates a single type containing i as a field, and 1000 instances.

In your case, instead of myfunc returning a new type for every invokation and returning the singleton instance of that type, it returns an instance of a type with the fields number, double and square.

Also, you can totally call fieldnames, you just have to call it on a type: fieldnames(typeof(myfunc(4))).

It's just an optimization, and it feels a little strange that you have code relying on the internals of how functions are represented in memory. You probably shouldn't do that.

Upvotes: 8

Related Questions