This question really has two parts
- I have observed that defining an outer constructor leads to infinite recursion
I understand why this is happening, but I do not know if I am doing something obviously stupid, which could be avoided to prevent this recursion from taking place.
Additionally
- Does this observation mean that inner constructors should always be preferred to outer constructors?
This actually leads to a second question which I think it makes more sense to ask separately. I will probably pick this up tomorrow.
This is my observation.
I tried to define an outer constructor for an example type which I found in the documentation.
struct OtherOrderedPair
x::Real
y::Real
end
function OtherOrderedPair(x::Real, y::Real)
if x > y
error("bad order 2")
else
OtherOrderedPair(x, y)
end
end
From the REPL:
julia> import("constructors.jl")
OtherOrderedPair # !!! why did it print this?
julia>OtherOrderedPair(1, 2) # correct order
ERROR: StackOverflowError:
...
Why this happens is (relatively) obvious. By default, the type OrderOrderedPair
defines a default inner constructor. It looks like this:
struct OtherOrderedPair
x::Real
y::Real
OtherOrderedPair(x::Real, y::Real) new(x, y)
end
This inner constructor is still provided by default even if an outer constructor is defined. From the docs,
If any inner constructor method is defined, no default constructor method is provided
However, when the outer constructor calls OtherOrderedPair
the multimethod resolution system (not sure what it is called) calls the same function again, because both the inner and outer constructor functions bind with equal precidence based on the types of their arguments alone.
It makes sense that the same function is called again rather than a different one. (Consider the case of three identical function signatures. If the same function was not called again there would be no way to figure out which of the remaining two should be called.)
So, some questions:
- Am I doing anything obviously stupid here which causes this recursion to take place when it need not?
- Is there a way to disambiguate between the two constructors, and break the infinite recursion?
- Is the observation here a reason to always prefer to define inner constructors rather than outer constructors?
Relating to this final point, the docs also say this
It is good practice to provide as few inner constructor methods as possible: only those taking all arguments explicitly and enforcing essential error checking and transformation. Additional convenience constructor methods, supplying default values or auxiliary transformations, should be provided as outer constructors that call the inner constructors to do the heavy lifting. This separation is typically quite natural.
but there is no explanation provided as to why this is the case .