Outdated documentation

The example at the end of https://docs.julialang.org/en/v1/manual/constructors/#Outer-only-constructors-1 does not work for me as shown. I’m using Julia 1.4.

According to the documentation this object construction should throw a MethodError, but it doesn’t:


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> SummedArray(Int32[1; 2; 3], Int32(6))
SummedArray{Int32,Int32}(Int32[1, 2, 3], 6)

I’m not sure if this is the right place to post this? Should I open an issue on GitHub instead?

The example works as documented for me. Are you running it in a clean session?

That said, generally it is best to open an issue, or even just make a PR on Github.

I would guess you’ve already added some methods to SummedArray (e.g., by running the prior example in the same session). Check out methods(SummedArray) to see them.

You are right, it is not a clean REPL but now I’m confused. What I did is that I executed the two examples one after the other:

julia> struct SummedArray{T<:Number,S<:Number}
                  data::Vector{T}
                  sum::S
              end
julia> SummedArray(Int32[1; 2; 3], Int32(6))
SummedArray{Int32,Int32}(Int32[1, 2, 3], 6)

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> SummedArray(Int32[1; 2; 3], Int32(6))
SummedArray{Int32,Int32}(Int32[1, 2, 3], 6)

I’m confused, because I was thinking that the type was redefined, and the first version is different from the second one. But it is not the case:

julia> struct SummedArray{T<:Number,S<:Number}
                         data::Vector{T}
                         sum::S
                     end
julia> First = SummedArray
SummedArray
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> Second = SummedArray
SummedArray
julia> result = SummedArray(Int32[1; 2; 3], Int32(6))
SummedArray{Int32,Int32}(Int32[1, 2, 3], 6)


julia> isa(result, Second)
true # I can understand this
julia> isa(result, First)
true # All right, this is suprising
julia> First === Second
true # Completely insane :-)
julia> methods(SummedArray)
# 2 methods for type constructor:
[1] (::Type{SummedArray})(data::Array{T,1}, sum::S) where {T<:Number, S<:Number} in Main at none:3
[2] (::Type{SummedArray})(a::Array{T,1}) where T in Main at none:9

So what happens if I give it a different name?

julia>  struct SummedArray2{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> SummedArray2 === First
false # This makes sense
julia> SummedArray2 === Second
false # This makes sense too

So maybe it means that if I “redeclare” the type with the same name, then it won’t create a new type, but rather “extend” it somehow.

What will happen if I try to change its structure?

julia> struct SummedArray{T<:Number,S<:Number}
                                data::Vector{T}
                                data2::Vector{T}
                                sum::S
                                function SummedArray(a::Vector{T}) where T
                                    S = widen(T)
                                    new{T,S}(a, sum(S, a))
                                end
                            end
ERROR: invalid redefinition of constant SummedArray
Stacktrace:
 [1] top-level scope at none:0

Obviously it is not a bug, but it was unexpected, at least for me.

It means that one can add inner constructors after the type was defined

julia> struct Test
           x :: Int64;
           function Test(x::Int64)
               return new(x)
           end
       end

julia> Test(1)
Test(1)

julia> Test(1.0)
ERROR: MethodError: no method matching Test(::Float64)
Closest candidates are:
  Test(::Int64) at none:4
Stacktrace:
 [1] top-level scope at none:0

julia> struct Test
                  x :: Int64;
                  # Please note that here we are adding an inner constructor,
                  # despite the fact that the Test type is already defined!
                  function Test(x::Float64)
                   return new(convert(Int64,x))
                  end
       end
julia> Test(1.0)
Test(1)

julia> methods(Test)
# 2 methods for type constructor:
[1] Test(x::Int64) in Main at none:4
[2] Test(x::Float64) in Main at none:6

julia> 

It might be considered a violation of the rules, but I don’t think that it is a design flaw or a problem. But definitely very interesting.

I learnt something new. Thank you!

Yes, redefining a struct's structure is a no-op as long as the fields don’t change. You’re not actually redefining it at all — Julia is just seeing that the struct itself hasn’t changed and thus allowing this operation (by effectively ignoring it) for your own workflow sanity.

It is a little funny how inner constructors interplay here (in that you can add them or change them), but I think it all makes sense.

Edit: it would be great to clarify that the second example there will only error in a fresh session (in the absence of example 1). Or simply using a new name for the second example to make it even more clear.

2 Likes