typename{...}(...)
(note the {}
part) is an inner constructor"?explicit inner constructor
?methods
for checking whether a method is an inner/outer constructor?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 typeT<: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 methods
ed 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"?
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.
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 ...
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 ..
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With