The first two sections below contain my first attempt at an explanation, and the third section contains a correction which I think gives a better and more correct explanation.
1) Semantics
Functions
If you have export foo
, where foo
is a function with multiple methods, you are exporting one object: the generic function foo
. When you declare new methods for foo
, you are extending the definition of the function foo
—you are not adding new objects called “methods”. In other words, new extensions to a function are called “methods”, but they are not new objects, they are extensions to the object foo
.
Evidence for this point of view can be seen from the output of typeof(foo)
:
julia> typeof(foo)
typeof(foo) (singleton type of function foo, subtype of Function)
The type typeof(foo)
is a singleton type, of which foo
is the only instance. In other words, the various methods do not have their own object instances (except perhaps as hidden implementation details).
Constructors
If you have export A
, where A
is a type, you are exporting two objects:
- The type
A
.
- The (constructor) function
A
.
2) Implementation Details
The following details may provide some insight, but I think they are considered to be implementation details rather than semantic guarantees of the language.
The constructors for a type are implemented by making the type object callable. You can even do this by hand. For example:
julia> struct A end
julia> (::Type{A})(x) = A()
julia> methods(A)
# 2 methods for type constructor:
[1] A() in Main at REPL[1]:1
[2] A(x) in Main at REPL[2]:1
julia> A(42)
A()
So when you write export A
, in practice you’re really just exporting the type object A
, and the constructors come along for free, since the type object A
is callable.
The situation for functions is similar. We can add methods to foo
by making objects of type typeof(foo)
callable:
julia> foo(x) = 2x
foo (generic function with 1 method)
julia> (::typeof(foo))(x, y) = x + y
julia> methods(foo)
# 2 methods for generic function "foo":
[1] foo(x) in Main at REPL[1]:1
[2] foo(x, y) in Main at REPL[2]:1
julia> foo(1, 2)
3
So when you write export foo
, in practice you’re really just exporting the function object foo
, and the methods come along for free, since the function object foo
is callable.
3) Corrections / clarifications
I think my explanation above is more of a post-hoc justification. It really comes down to the passage from the docs that you quoted:
So, all that export
really does is take a variable binding in a module and make it available in another module when you using
the first module. A variable can only be bound to one value (object). In the case of types and functions, there are multiple ways of calling that object, but it is still only one object. And the fact that types are callable really is a language guarantee, rather than an implementation detail as I wrote above.