Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What on earth is an inner constructor?

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"?

like image 807
Gnimuc Avatar asked Jul 26 '17 13:07

Gnimuc


People also ask

What is a constructor in Julia?

Constructors are functions that create new objects – specifically, instances of Composite Types. In Julia, type objects also serve as constructor functions: they create new instances of themselves when applied to an argument tuple as a function.

What is a struct in Julia?

Structures (previously known in Julia as "Types") are, for the most (see later for the difference), what in other languages are called classes, or "structured data": they define the kind of information that is embedded in the structure, that is a set of fields (aka "properties" in other languages), and then individual ...


1 Answers

By the way of example, suppose you want to define a type to represent even numbers:

julia> struct Even           e::Int        end  julia> Even(2) Even(2) 

So far so good, but you also want the constructor to reject odd numbers and so far Even(x) does not:

julia> Even(3) Even(3) 

So you attempt to write your own constructor as

julia> Even(x) = iseven(x) ? Even(x) : throw(ArgumentError("x=$x is odd")) Even 

and ... drum roll, please ... It does not work:

julia> Even(3) Even(3) 

Why? Let's ask Julia what she has just called:

julia> @which Even(3) Even(e::Int64) in Main at REPL[1]:2 

This is not the method that you defined (look at the argument name and the type), this is the implicitly provided constructor. Maybe we should redefine that? Well, don't try this at home:

julia> Even(e::Int) = iseven(e) ? Even(e) : throw(ArgumentError("e=$e is odd")) Even julia> Even(2) ERROR: StackOverflowError: Stacktrace:  [1] Even(::Int64) at ./REPL[11]:0  [2] Even(::Int64) at ./REPL[11]:1 (repeats 65497 times)  

We've just created an infinite loop: we redefined Even(e) to recursively call itself. We are now facing a chicken and egg problem: we want to redefine the implicit constructor, but we need some other constructor to call in the defined function. As we've seen calling Even(e) is not a viable option.

The solution is to define an inner constructor:

julia> struct Even           e::Int           Even(e::Int) = iseven(e) ? new(e) : throw(ArgumentError("e=$e is odd"))        end   julia> Even(2) Even(2)  julia> Even(3) ERROR: ArgumentError: e=3 is odd .. 

Inside an inner constructor, you can call the original implicit constructor using the new() syntax. This syntax is not available to the outer constructors. If you try to use it, you'll get an error:

julia> Even() = new(2) Even  julia> Even() ERROR: UndefVarError: new not defined  .. 
like image 187
Alexander Belopolsky Avatar answered Sep 20 '22 20:09

Alexander Belopolsky