What does new in general do in inner constructor? For example in this code in documentation how new know the work( here create instance if x>y) it should do? Constructors · The Julia Language
julia> struct OrderedPair
x::Real
y::Real
OrderedPair(x,y) = x > y ? error("out of order") : new(x,y)
end
What are the advantages of using inner constructor?
[An inner constructor method] has access to a special locally existent function called new that creates objects of the block’s type.
The fields you write in the struct definition is all new needs to instantiate the object. new instantiation is only available inside the struct definition so it can specially lowered for the type; outside, it can only be treated as another global name.
Outer constructor methods must call inner constructor methods but can be written from other modules. So, implement inner constructor methods for the basics then outer constructors on different input types for flexibility.
To answer the second question: you often do not actually need an inner constructor. The major reason to have an inner constructer is that you can ensure that certain constraints are fulfilled, even if another outer constructor is added later.
I.e. in your example it is now impossible to create any OrderedPair that is not ordered.
Typically, I recommend starting with the default constructors when writing your own code. Case (2) is very specific, and if you are wondering why you need it then you probably don’t.
Case (1) is arguably not needed if the actual arguments passed to the default constructor never violate the invariants. In practice, if you code for yourself or for a small collective, that’ll likely to be true. Basically, you only need those safeguards if you decide to make your code available to a large audience.
Invariant is some property that must hold true for a program to be correct. That’s not Julia terminology, that’s a general CS term (Invariant (mathematics) - Wikipedia).
For types, it’s some property that must be true for all objects of a given type.
A semi-realistic example where one faces actual design choices, each with its own pros and cons.
Let’s say we design a type for a QR decomposition and we want to store Q and R matrices explicitly:
struct QRDecomposition
Q::Matrix{Float64}
R::Matrix{Float64}
end
Now, Q must be an orthogonal matrix, i.e. Q^TQ = I, R must be upper-triangular and the sizes of the matrices must be compatible. How do we guarantee that?
We write an inner constructor QRDecomposition(Q, R) where we check those properties – orthogonality check in particular is quite expensive due to matrix product, so that the constructor will incur some unavoidable overhead.
We only allow the construction of the object by performing an actual QR decomposition of some matrix: the only inner constructor will be QRDecomposition(M) will perform the decomposition of matrix M. A plausible solution, but then we tie the users to a hardcoded decomposition algorithm.
We just keep the default constructor QRDecomposition(Q, R) but declare it an implementation detail. The public interface will be using a function qr(M). That way, we don’t guarantee the consistency of QRDecomposition objects in general, only of those returned by the function qr. On the other hand, users can now construct such objects from pre-computed decompositions without invariant-checking overhead. Also, users can easily add their own functions to optimize QR decomposition for special types of matrices if they have such needs.
In practice, option (3) is quite common: the type constructor is not supposed to be used directly; the most common case is covered by a helper function; advanced users can play with the low-level interface at their own risk.