Difference between two forms of inner constructors


#1

I’m defining my own type like below:

type KOM{T}
    model::Trie{T,Int64}
    context::Vector{T}
    cxt_length::Int64
  
    # Constructor 1
    # KOM{T}( _c::Int ) where{T} = new( Trie{T,Int64}(), Vector{T}(), _c )
    # Constructor 2
    # (::Type{KOM{T}}){T}(_c::Int64) = new{T}( Trie{T,Int64}(), Vector{T}(), _c );
end

I would like to know what is the different between the two types of constructers I used here (both are commented).
I don’t know which one is correct, because I sometimes randomly gets error while using one, but no error with other :confused:. I know this random error is very vauge, but I think understanding the difference will help me to understand the errors.


#2

Use the first one,

KOM{T}( _c::Int ) where{T} = new( Trie{T,Int64}(), Vector{T}(), _c )

The two forms you provide are different in several respects:

  1. the first uses {T} ... where {T}, which is the new recommended syntax, while the second one uses {T}( ... {T}), which has been deprecated,
  2. the second one dispatches on the type, which is OK but not necessary here,
  3. the second one calls new with a type parameter, which is not necessary here (implied by context).

If you are getting a error, it is best to provide a minimal working example (MWE). These kind of errors are not “random”, but usually easily reproducible.

Also see the latest release notes on constructors, and the two issues referenced there (if you want to learn about the history of this syntax).


#3

Not yet. Also the form that is planing to be deprecated is ...{T}(...) in favor of ...(...) where {T}.

Not sure what do you mean here. The first one is also dispatching on the type.

No it’s not implied by context.

The main difference is that the first one is the recommanded syntax for inner constructor, it’s the same as the one on older version but without implicitly assuming a type parameter. The second one is almost the only way to get cross-version support though. Both should work when writen correctly but that is not necessarily trivial to do so if you get an error, posting the actual code would be useful.


#4

I was imprecise, I meant that it uses a syntax usually used to make a value callable.

In v0.6.0,

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

WARNING: deprecated syntax "inner constructor Foo(...) around REPL[0]:3".
Use "Foo{#s4}(...) where #s4" instead.

so I think it is being deprecated.


#5

That’s exactly what the other syntax mean too.

No it’s not. The old inner constructor syntax is deprecated, using ...{T}(...) to specify type parameter, which is the only thing constructor 2 is doing, is not. Bottom line is that constructor 2, along with all features of the language and syntax it uses, it not deprecated.


#6

Thanks both of for your replies. But I’m more puzzled by the observations below.

I define a new type as

type MyType1{T}
    x::Vector{T}
    c::Int64

    MyType1{T}() where {T} = new( Vector{T}(), 1 );        # Constructor-1
    (::Type{MyType1{T}}){T}() = new{T}( Vector{T}(), 2 );  # Constructor-2
end

# Now I can do
julia> t1 = MyType1{Int64}()
MyType1{Int64}(Int64[], 2)    # The second constructor is called( c=2 here )

julia> which( MyType1{Int64}, () )
(::Type{MyType1{T}})() where T in Main at REPL[1]:6

Now, say if I define the constructors in opposite order, like below

type MyType2{T}
    x::Vector{T}
    c::Int64

    (::Type{MyType2{T}}){T}() = new{T}( Vector{T}(), 2 );  # Constructor-2
    MyType2{T}() where {T} = new( Vector{T}(), 1 );        # Constructor-1
end

# Now I can do
julia> t2 = MyType2{Int64}()
MyType2{Int64}(Int64[], 1) # This is constructor-1 ( c=1 )

So far it seems like the latest defined constructor is taking the action.
But if I do

julia> which( MyType2{Int64}, () )
(::Type{MyType2{T}})() where T in Main at REPL[4]:6

:confused:
which() tells me it calling constructor-2.

Why is this happening? :thinking:


#7

They have the same signature, so you are overwriting the first one with the last one.

This is not related to constructors or parametric types. You can replicate this with

struct Foo end
Foo() = 1 
(::Type{Foo})() = 2
Foo() # 2
methods(Foo) # two methods, the default just calls convert

#8

(also in Julia you don’t need to close lines with ; when you use linebreaks)


#9

What about this?

struct MyType1
    x::Int64
    MyType1() = new( 1 );  # Constructor-1
    (::Type{MyType1})() = new( 2 );  # Constructor-2
end

julia> methods( MyType1 )
# 2 methods for generic function "(::Type)":
MyType1() in Main at REPL[1]:4
(::Type{T})(arg) where T in Base at sysimg.jl:24
# Till here okay
julia> which( MyType1, () )
MyType1() in Main at REPL[3]:1
# So 'which()' tells that it's going to call Constructor-1
julia> MyType1()
MyType1(2)
# but called constructor-2, which is the latest defined one: ( x=2 here )
struct MyType2
    x::Int64
    (::Type{MyType2})() = new( 2 );  # Constructor-2
    MyType2() = new( 1 );  # Constructor-1
end

julia> methods( MyType2 )
# 2 methods for generic function "(::Type)":
MyType2() in Main at REPL[7]:4
(::Type{T})(arg) where T in Base at sysimg.jl:24

julia> which( MyType2, () )
MyType2() in Main at REPL[7]:4
# Here again 'which()' reports constructor-1
julia> MyType2()
MyType2(1)
# and calls constructor-1 ( x=1 here ), which is the latest defintion

So my doubt is about this behaviour of which().


#10

:stuck_out_tongue_winking_eye: Got used to C++


#11

No it doesn’t, and this is exactly what I mean by

It’s telling you that it’s calling constructor 2 they are exactly the same and the printing is normalized. I’m not sure why the line number shows up incorrectly for you but this shows the correct linenumber for me.

julia> struct MyType1
           x::Int64
           MyType1() = new( 1 );  # Constructor-1
           (::Type{MyType1})() = new( 2 );  # Constructor-2
       end

julia> @which MyType1()
MyType1() in Main at REPL[2]:4 

Note that the constructor 2 is on line 4.