In the following code, I have a composite type, and in my real code several of the fields are matrices. In this example is only 1. I keep getting stack overflow when I try constructing the composite type. Here is the code sample:
struct Tables
eij::Array{Float64,2}
end
Tables(eij::Array{Float64,2}) = Tables(eij) # works if this is commented out
x = 5 # arbitrary matrix dimension
e_ij = Array{Float64,2}(undef,x,x)
for i=1:x
for j=1:x
e_ij[i,j] = i*j/2.3 #dummy numbers, but not the case in real code
end
end
vdwTable = Tables([e_ij[i,j] for i=1:x,j=1:x])
I use the temporary variable e_ij
to make the matrix first since I don't want the composite Tables
to be mutable. So, my reasoning is that by generating the tables first in dummy variables like e_ij
, I can then initialize the immutable Tables
that I really want.
If I comment out the outer constructor for the struct Tables
it works. However, I actually want to have several different outer constructors for cases where different fields are not passed data to be initialized. In these cases, I want to give them default matrices.
The error I get is the following: ERROR: LoadError: StackOverflowError:
on
the line vdwTable = Tables([e_ij[i,j] for i=1:x,j=1:x])
When you define a composite type, an inner constructor is automatically defined, so this:
struct Tables
eij::Array{Float64,2}
end
is equivalent to this:
struct Tables
eij::Array{Float64,2}
Tables(eij::Array{Float64,2}) = new(eij)
end
When you define this outer constructor
Tables(eij::Array{Float64,2}) = Tables(eij)
you get in the way of the inner constructor. Your outer constructor just calls itself recursively until you get a stack overflow.
Doing this, on the other hand,
Tables(eij) = Tables(eij)
is actually equivalent to this:
Tables(eij::Any) = Tables(eij)
so when you subsequently call
vdwTable = Tables([e_ij[i,j] for i=1:x,j=1:x])
it then just ignores your outer constructor, because there is a more specific method match, namely the inner constructor. So that particular outer constructor is quite useless, it will either be ignored, or it will recurse until stack overflow.
The simplest solution is: just don't make an outer constructor. If you do need an outer one to enforce some conditions, make sure that it doesn't shadow the inner constructor by having the same type signature. For example,
Tables() = Tables(zero(5, 5))
should work.
I would probably do it like this, though:
struct Tables
eij::Array{Float64,2}
Tables(eij=zeros(5, 5)) = new(eij)
end
For your second example, with two fields, you can try this:
struct Tables
eij::Array{Float64,2}
sij::Array{Float64,2}
Tables(eij=zeros(5,5), sij=zeros(5,5)) = new(eij, sij)
end
Your inputs will be converted to Float64
matrices, if that is possible, otherwise an exception will be raised.
DNF gave a proper explanation so +1. I would just like to add one small comment (not an answer to the question but something that is relevant from my experience), that is too long for a comment.
When you omit specifying an inner constructor yourself Julia automatically defines one inner and one outer constructor:
julia> struct Tables
eij::Array{Float64,2}
end
julia> methods(Tables)
# 2 methods for generic function "(::Type)":
[1] Tables(eij::Array{Float64,2}) in Main at REPL[1]:2
[2] Tables(eij) in Main at REPL[1]:2
while defining an inner constructor suppresses definition of the outer constructor:
julia> struct Tables
eij::Array{Float64,2}
Tables(eij::Array{Float64,2}) = new(eij)
end
julia> methods(Tables)
# 1 method for generic function "(::Type)":
[1] Tables(eij::Array{Float64,2}) in Main at REPL[1]:3
So the cases are not 100% equivalent. The purpose of the auto-generated outer constructor is to perform an automatic conversion of its argument if it is possible, see e.g. (this is the result in the first case - when no inner constructor was defined):
julia> @code_lowered Tables([true false
true false])
CodeInfo(
1 ─ %1 = (Core.apply_type)(Main.Array, Main.Float64, 2)
│ %2 = (Base.convert)(%1, eij)
│ %3 = %new(Main.Tables, %2)
└── return %3
)
while in the second case the same call would throw a method error.
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