May 14, 2021 Julia
2. The external construction method
3. The internal construction method
Constructor 1 is a function that constructs a new object, a new composite type instance. Constructing type objects:
type Foo
bar
baz
end
julia> foo = Foo(1,2)
Foo(1,2)
julia> foo.bar
1
julia> foo.baz
2
Recursive data structures, especially self-referenced data structures, often need to be constructed as incomplete and then perfected step by step. W e may also sometimes want to construct objects more easily with fewer or different types of parameters. Julia's constructors can satisfy a variety of requirements, including these.
About naming: Although "constructors" are often used to describe functions that create new objects, they are often misused in specific construction methods. In general, it is easy to infer from the context whether it is a "constructor" or a "construction method".
The constructor, like other functions in Julia, behaves depending on the combination of the behavior of all its methods. T
herefore, you can add new performance to the constructor by defining new methods.
The following example adds a new construction method to
Foo
entering only one parameter and assigning the parameter value to
bar
and
baz
Foo(x) = Foo(x,x)
julia> Foo(1)
Foo(1,1)
Add
Foo
zero-parameter construction method and
bar
to bar and
baz
fields:
Foo() = Foo(0)
julia> Foo()
Foo(0,0)
This appended construction method is called the external construction method. It can only call other construction methods to construct instances by providing default values.
The internal construction method is similar to the external construction method, but with two differences:
new
function to construct an object of the type of declaration block
For example, to declare a type that holds a real pair, and the first number is not greater than the second number:
type OrderedPair
x::Real
y::Real
OrderedPair(x,y) = x > y ? error("out of order") : new(x,y)
end
The
OrderedPair
object is constructed only when
x <= y
julia> OrderedPair(1,2)
OrderedPair(1,2)
julia> OrderedPair(2,1)
ERROR: out of order
in OrderedPair at none:5
All external construction methods end up calling internal construction methods.
Of course, if a type is
immutable
the structure of its constructor cannot be changed.
This is important when determining whether a type should be immutable.
If an internal construction method is defined, Julia no longer provides the default construction method. T
he default construction method is equivalent to a custom internal construction method, which passes all fields of an object as arguments (which should be specific if the domain has a
new
finally returns the resulting object:
type Foo
bar
baz
Foo(bar,baz) = new(bar,baz)
end
This declaration is equivalent to
Foo
which did not indicate the internal construction method earlier.
The following two are also equivalent, one using the default construction method and one ingesting the construction method:
type T1
x::Int64
end
type T2
x::Int64
T2(x) = new(x)
end
julia> T1(1)
T1(1)
julia> T2(1)
T2(1)
julia> T1(1.0)
T1(1)
julia> T2(1.0)
T2(1)
Internal construction methods can be written without writing. Things like providing default values should be written as external construction methods that call internal construction methods.
Consider the following recursive type declarations:
type SelfReferential
obj::SelfReferential
end
If
a
is
SelfReferential
you can construct the second instance as follows:
b = SelfReferential(a)
But how do you construct the first instance when there are no instances to provide a valid value for the
obj
domain?
The only workaround
obj
SelfReferential
partial initialization instance of the obj domain, which is used as a valid
obj
as itself.
When constructing a partial initialization object, Julia allows the
new
handle arguments that are fewer than the number of domains of that type, returning objects that are not initialized by the partial domain. A
t this point, the internal constructor can use the incomplete object and complete its initialization before returning.
In the following example, when
SelfReferential
we use the zero-reference internal construction method
obj
field pointing to itself:
type SelfReferential
obj::SelfReferential
SelfReferential() = (x = new(); x.obj = x)
end
This construction method can run and construct self-leading objects:
julia> x = SelfReferential();
julia> is(x, x)
true
julia> is(x, x.obj)
true
julia> is(x, x.obj.obj)
true
The internal construction method is best to return a fully initialized object, but you can also return a partially initialized object:
julia> type Incomplete
xx
Incomplete() = new()
end
julia> z = Incomplete();
Although you can construct objects that are not initialized, reading references that are not initialized will report an error:
julia> z.xx
ERROR: access to undefined reference
This avoids constantly checking
null
value. H
owever, the domains of all objects are references. J
ulia considers some types to be "normal data", i.e. their data is independent and does not refer to other objects. A
normal data type is made up of a bit type or an immedic data structure of other common data types, such
Int
The initial content of a normal data type is undefined: ::
julia> type HasPlain
n::Int
HasPlain() = new()
end
julia> HasPlain()
HasPlain(438103441441)
Arrays made up of ordinary data types have the same behavior.
In an internal construction method, incomplete objects can be passed to other functions to delegate full initialization:
type Lazy
xx
Lazy(v) = complete_me(new(), v)
end
If
complete_me
other called function tries to read the
Lazy
xx
domain before it is initialized, an error is reported immediately.
Examples of paramethy construction methods:
julia> type Point{T<:Real}
x::T
y::T
end
## implicit T ##
julia> Point(1,2)
Point{Int64}(1,2)
julia> Point(1.0,2.5)
Point{Float64}(1.0,2.5)
julia> Point(1,2.5)
ERROR: `Point{T<:Real}` has no method matching Point{T<:Real}(::Int64, ::Float64)
## explicit T ##
julia> Point{Int64}(1,2)
Point{Int64}(1,2)
julia> Point{Int64}(1.0,2.5)
ERROR: InexactError()
julia> Point{Float64}(1.0,2.5)
Point{Float64}(1.0,2.5)
julia> Point{Float64}(1,2)
Point{Float64}(1.0,2.0)
The paramethy construction method above is equivalent to the following declaration:
type Point{T<:Real}
x::T
y::T
Point(x,y) = new(x,y)
end
Point{T<:Real}(x::T, y::T) = Point{T}(x,y)
The internal construction method defines
Point{T}
not the method of
Point
constructor.
Point
not a specific type and cannot have an internal construction method.
The external construction method defines
Point
method for Point.
You can construct
1
the integer value 1 to
1.0
julia> Point(x::Int64, y::Float64) = Point(convert(Float64,x),y);
This allows the following example to work correctly:
julia> Point(1,2.5)
Point{Float64}(1.0,2.5)
julia> typeof(ans)
Point{Float64} (constructor with 1 method)
However, the following example will still report an error:
julia> Point(1.5,2)
ERROR: `Point{T<:Real}` has no method matching Point{T<:Real}(::Float64, ::Int64)
In fact, you only need to define the following external construction methods:
julia> Point(x::Real, y::Real) = Point(promote(x,y)...);
promote
function converts all its arguments to the same type.
Now all real parameters work:
julia> Point(1.5,2)
Point{Float64}(1.5,2.0)
julia> Point(1,1//2)
Point{Rational{Int64}}(1//1,1//2)
julia> Point(1.0,1//2)
Point{Float64}(1.0,0.5)
Here's the beginning of the rational.jl file, which achieves Julia's score:
immutable Rational{T<:Integer} <: Real
num::T
den::T
function Rational(num::T, den::T)
if num == 0 && den == 0
error("invalid rational: 0//0")
end
g = gcd(den, num)
num = div(num, g)
den = div(den, g)
new(num, den)
end
end
Rational{T<:Integer}(n::T, d::T) = Rational{T}(n,d)
Rational(n::Integer, d::Integer) = Rational(promote(n,d)...)
Rational(n::Integer) = Rational(n,one(n))
//(n::Integer, d::Integer) = Rational(n,d)
//(x::Rational, y::Integer) = x.num // (x.den*y)
//(x::Integer, y::Rational) = (x*y.den) // y.num
//(x::Complex, y::Real) = complex(real(x)//y, imag(x)//y)
//(x::Real, y::Complex) = x*y'//real(y*y')
function //(x::Complex, y::Complex)
xy = x*y'
yy = real(y*y')
complex(real(xy)//yy, imag(xy)//yy)
end
Examples of plulue scores:
julia> (1 + 2im)//(1 - 2im)
-3//5 + 4//5*im
julia> typeof(ans)
Complex{Rational{Int64}} (constructor with 1 method)
julia> ans <: Complex{Rational}
false