What on earth is an inner constructor?

Viewed 4971

TL;DR:

  1. what's the accurate definition of inner constructors? In Julia-v0.6+, is it right to say "any constructor that can be called with the signature typename{...}(...)(note the {} part) is an inner constructor"?
  2. As discussed in the comment below, is the outer-only constructor actually an explicit inner constructor?
  3. Is it right to use methods for checking whether a method is an inner/outer constructor?
  4. What's the difference between the default constructors that automatically defined by Julia and the corresponding ones explicitly defined by users?

BTW, I know how to use and when to use an inner constructor. I knew what an inner constructor is until the outer-only constructors come in and muddied the waters. :(

Let's recall some statements from the doc:

1. Outer Constructor Methods

A constructor is just like any other function in Julia in that its overall behavior is defined by the combined behavior of its methods.

2. Inner Constructor Methods

An inner constructor method is much like an outer constructor method, with two differences: 1. It is declared inside the block of a type declaration, rather than outside of it like normal methods. 2. It has access to a special locally existent function called new that creates objects of the block's type.

3. Parametric Constructors

Without any explicitly provided inner constructors, the declaration of the composite type Point{T<:Real} automatically provides an inner constructor, Point{T}, for each possible type T<:Real, that behaves just like non-parametric default inner constructors do. It also provides a single general outer Point constructor that takes pairs of real arguments, which must be of the same type.

I found inner constructor methods can't be directly observed by methods, even methods(Foo{Int}) works, it's actually not "just like any other function", common generic functions cannot be methodsed in this way.

julia> struct Foo{T}
    x::T
end

julia> methods(Foo)
# 2 methods for generic function "(::Type)":
(::Type{Foo})(x::T) where T in Main at REPL[1]:2  # outer ctor  「1」
(::Type{T})(arg) where T in Base at sysimg.jl:24  # default convertion method「2」

julia> @which Foo{Int}(1) # or methods(Foo{Int})
(::Type{Foo{T}})(x) where T in Main at REPL[1]:2 # inner ctor 「3」

However, the outer-only constructors adds another wrinkle to the constructor story:

julia> struct SummedArray{T<:Number,S<:Number}
           data::Vector{T}
           sum::S
           function SummedArray(a::Vector{T}) where T
               S = widen(T)
               new{T,S}(a, sum(S, a))
           end
       end
julia> methods(SummedArray)
# 2 methods for generic function "(::Type)":
(::Type{SummedArray})(a::Array{T,1}) where T in Main at REPL[1]:5 # outer ctor「4」
(::Type{T})(arg) where T in Base at sysimg.jl:24

Hmmm, an outer constructor IN a type declaration block, and it calls new as well. I guess the purpose here is just to prevent Julia from defining the default inner-outer constructor pair for us, but is the second statement from the documentation still true in this case? It's confusing to new users.

Here, I read another form of inner constructors:

julia> struct Foo{T}
     x::T
     (::Type{Foo{T}})(x::T) = new{T}(x) 
   end

julia> methods(Foo)
# 1 method for generic function "(::Type)":
(::Type{T})(arg) where T in Base at sysimg.jl:24

julia> methods(Foo{Int})
# 2 methods for generic function "(::Type)":
(::Type{Foo{T}})(x::T) where T in Main at REPL[2]:3  「5」
(::Type{T})(arg) where T in Base at sysimg.jl:24

It's far from the canonical form Foo{T}(x::T) where {T} = new(x) but it seems the results are quite the same.

So my question is what's the accurate definition of inner constructors? In Julia-v0.6+, is it right to say "any constructor that can be called with the signature typename{...}(...)(note the {} part) is an inner constructor"?

1 Answers
Related