Reputation: 3850
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
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