Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Julia constructor


May 14, 2021 Julia


Table of contents


The constructor

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 external 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

The internal construction method is similar to the external construction method, but with two differences:

  1. It is declared inside the type declaration block, not externally, as is the normal method
  2. It calls a locally 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.

Partial initialization

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.

Paramethy construction methods

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)

Case: Score

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