Type inference: regression in 1.0?


#1

The following code works well in v0.6.4 but fails in v1.0:

module Scratch

using StaticArrays
abstract type AbstractX end

struct Foo{E<:AbstractX}
    v::Vector{E}
    function Foo{E}(v::Vector{E}) where {E}
        new(copy(v))
    end
end

struct ConcreteX{N,T} <: AbstractX
    w::SVector{N, T}
    function ConcreteX{N,T}(w::SVector{N, T}) where {N,T}
        new(w)
    end
end

function ConcreteX(w::SVector{N, T}) where {N,T}
     ConcreteX{N,T}(w)
end

const Bar{N,T}=Foo{ConcreteX{N,T}}

e1= ConcreteX(@SVector [1, 2])
e2= ConcreteX(@SVector [3, 4])

Bar([e1, e2])

end

The error message is:

ERROR: LoadError: MethodError: no method matching Main.Scratch.Foo{Main.Scratch.ConcreteX{N,T} where T} where N(::Array{Main.Scratch.ConcreteX{2,Int64},1})

(at the line 29).
Update
Explicit type specification
Bar{2,Int}([e1, e2])
is a possible workaround. But I still do no see how one can help Julia to infer the type parameters of Bar.


#2

Type inference has nothing to do with this, it’s more of a subtyping question. You probably want something like:

module Scratch

       using StaticArrays
       abstract type AbstractX end

       struct Foo{E<:AbstractX}
           v::Vector{E}
           function (::Type{<:Foo})(v::Vector{E}) where {E}
               new{E}(copy(v))
           end
       end

       struct ConcreteX{N,T} <: AbstractX
           w::SVector{N, T}
           function ConcreteX{N, T}(w::SVector{N, T}) where {N,T}
               new{N,T}(w)
           end
       end

       function ConcreteX(w::SVector{N, T}) where {N,T}
            ConcreteX{N,T}(w)
       end

       const Bar{N,T}=Foo{ConcreteX{N,T}}

       e1= ConcreteX(@SVector [1, 2])
       e2= ConcreteX(@SVector [3, 4])

       x = Bar([e1, e2])

       end

#3

I am a bit confused with this construction:

I thought that new() is usable in inner constructors only, and the above syntax is that of a callable type.


#4

This is a case where, IMHO, giving things names only makes it more confusing…

In any case, new is usuable for functions defined within the type, period.


#5

I like this syntax, but is it documented somewhere?


#6

Yes, new{ } is described in the manual chapter on constructors.

The key to understanding this is that there’s no real distinction between a constructor and a “callable type”. Every method definition can be written two ways: either by specifying the (singleton) instance of the called thing, or by specifying its type. So function f(x) can also be written as function (::typeof(f))(x). (Detail: the second form requires f to be assigned already.) For types, it looks like function Int(x) or function (::Type{Int})(x) but the idea is the same.

It’s quite possible the manual chapter should be rewritten not to refer to “inner” and “outer” constructors. The only distinctions that matter are (1) what type is the method defined for, and (2) is it written inside the struct block, giving it access to new. These two are completely orthogonal.


#7

Thank you, this clarifies the situation. As for the manual, I guess that the issue is larger than the story of “inner” and “outer” constructors. The very notion of constructor seems to be quite different in Julia from what people are used to in other languages. As far as I understand, in Julia it is not a hard-coded feature of the language, but rather an idiom (a particular case of a “callable thing”).