Reputation: 10510
Trying to understand parametric types and the new
function available for inner methods. The manual states "special function available to inner constructors which created a new object of the type". See the section of the manual on new here and the section of the manual on inner constructor methods here.
Consider an inner method designed to calculate the sum of x
, where x
could be, say, a vector or a tuple, and is given the parametric type T
. A natural thing to want is for the type of the elements of x
to be inherited by their sum s
. I don't seem to need new
for that, correct?
struct M{T}
x::T
s
function M(x)
s = sum(x)
x,s
end
end
julia> M([1,2,3])
([1, 2, 3], 6)
julia> M([1.,2.,3.])
([1.0, 2.0, 3.0], 6.0)
julia> typeof(M([1.,2.,3.]))
Tuple{Vector{Float64}, Float64}
Edit: Correction! I intended to have the last line of the inner constructor be M(x,s)
... It's still an interesting question, so I won't correct it. How does M(x,s)
differ from new{typeof(x)}(x,s)
?
One usage of new
I have seen is in combination with typeof()
, something like:
struct M{T}
x::T
s
function M(x)
s = sum(x)
new{typeof(x)}(x,s)
end
end
julia> M([1,2,3])
M{Vector{Int64}}([1, 2, 3], 6)
julia> M([1.,2.,3.])
M{Vector{Float64}}([1.0, 2.0, 3.0], 6.0)
What if wanted to constrain s
to the same type as x
? That is, for instance, if x
is a vector, then s
should be a vector (in this case, a vector of one element). How would I do that? If I replace the last line of the inner constructor with x, new{typeof(x)}(s)
, I get the understandable error:
MethodError: Cannot `convert` an object of type Int64 to an object of type Vector{Int64}
Upvotes: 6
Views: 1538
Reputation: 7664
Here are the rules:
If you are writing an outer constructor for a type M
, the constructor should return an instance of M
by eventually calling the inner constructor, like this: M(<args>)
.
If you are writing an inner constructor, this will override the default inner constructor. So you must return an instance of M
by calling new(<args>)
.
The new
"special function" exists to allow the construction of a type that doesn't have a constructor yet. Observe the following example:
julia> struct A
x::Int
function A(x)
A(x)
end
end
julia> A(4)
ERROR: StackOverflowError:
Stacktrace:
[1] A(::Int64) at ./REPL[3]:4 (repeats 79984 times)
This is a circular definition of the constructor for A
, which results in a stack overflow. You cannot pull yourself up by your bootstraps, so Julia provides the new
function as a way to circumvent this problem.
You should provide the new
function with a number of arguments equal to the number of fields in your struct. Note that the new
function will attempt to convert the types of its inputs to match the declared types of the fields of your struct:
julia> struct B
x::Float64
B(x) = new(x)
end
julia> B(5)
B(5.0)
julia> B('a')
B(97.0)
julia> B("a")
ERROR: MethodError: Cannot `convert` an object of type String to an object
of type Float64
(The inner constructor for B
above is exactly the same as the default inner constructor.)
When you're defining parametric types, the new
function must be provided with a number of parameters equal to the number of parameters for your type (and in the same order), analogously to the default inner constructor for parametric types. First observe how the default inner constructor for parametric types is used:
julia> struct Foo{T}
x::T
end
julia> Foo{String}("a")
Foo{String}("a")
Now if you were writing an inner constructor for Foo
, instead of writing Foo{T}(x)
inside the constructor, you would replace the Foo
with new
, like this: new{T}(x)
.
You might need typeof
to help define the constructor, but often you don't. Here's one way you could define your M
type:
struct M{I, T}
x::I
s::T
function M(x::I) where I
s = sum(x)
new{I, typeof(s)}(x, s)
end
end
I'm using typeof
here so that I
could be any iterable type that returns numbers:
julia> typeof(M(1:3))
M{UnitRange{Int64},Int64}
julia> g = (rand() for _ in 1:10)
Base.Generator{UnitRange{Int64},var"#5#6"}(var"#5#6"(), 1:10)
julia> typeof(M(g))
M{Base.Generator{UnitRange{Int64},var"#5#6"},Float64}
Note that providing the parameters for your type is required when you are using new
inside an inner constructor for a parametric type:
julia> struct C{T}
x::Int
C(x) = new(x)
end
ERROR: syntax: too few type parameters specified in "new{...}" around REPL[6]:1
Upvotes: 2
Reputation: 70267
Remember, a constructor is designed to construct something. Specifically, the constructor M
is designed to construct a value of type M
. Your example constructor
struct M{T}
x::T
s
function M(x)
s = sum(x)
x,s
end
end
means that the result of evaluating the expression M([1 2 3])
is a tuple, not an instance of M
. If I encountered such a constructor in the wild, I'd assume it was a bug and report it. new
is the internal magic that allows you to actually construct a value of type M
.
It's a matter of abstraction. If you just want a tuple in the first place, then forget about the structure called M
and just define a function m
at module scope that returns a tuple. But if you intend to treat this as a special data type, potentially for use with dynamic dispatch but even just for self-documentation purposes, then your constructor should return a value of type M
.
Upvotes: 1