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

Julia type


May 14, 2021 Julia


Table of contents


Type

In Julia, if the type is omitted, the value can be any type. Adding types can significantly improve performance and system stability.

The characteristic of the Julia type system is that specific types cannot be sub-types of specific types, all specific types are final, and they can have abstract types as parent types. Other advanced features are:

  • Indestrunging objects from non-object values: All values in Julia are a typed object that belongs to a single, fully connected type diagram, where each node is a type.
  • There is no "compile-time type": a program runs with only its actual type, which is referred to as a "run-time type" in an object-oriented programming language.
  • Values have types, variables don't have types -- variables are just names that bind values.
  • Both abstract and concrete types can be parameterized by other types and values. S pecifically, parameterization can be a symbol, an arbitrary value of a type with an isbits return value of true (essentially, these numbers, like integers or Boolean values, are stored in a similar form to a data type or struct in C, and do not point to other data), or they can be metagroups. If type parameters do not need to be used or restricted, you can omit unstritten.

Julia's type systems are designed to be effective and expressive, both intuitive and not exaggerated. M any Julia programmers may never feel the need to specify the type. However, some programs become clearer, simpler, faster, and more robust because of the type of declaration.

The type declaration

:: Operators can be used to attach type comments to expressions and variables in a program. There are two reasons for this:

  1. As an assertion, help confirm that the program is functioning properly
  2. Provide the compiler with additional type information to help improve performance

:: When an operator is placed after an expression that represents a value, it reads "The former is an instance of the latter" and is used to assert whether the left expression is an instance of the right expression. I f the right side is a specific type, this type should be an instance on the left. I f the right side is an abstract type, the left side should be the value of an instance of a specific type, which is a sub-type of this abstract type. If the type is asserted to be false, an exception is thrown, otherwise the left value is returned:

    julia> (1+2)::FloatingPoint
    ERROR: type: typeassert: expected FloatingPoint, got Int64

    julia> (1+2)::Int
    3

You can make type assertions where any expression is located. :: most common usage is as an assertion in a function/method signature, such as f(x::Int8) = ... (view method).

:: When an operator follows a variable name in the context of an expression, it declares that the variable should be of a type, somewhat similar to a type declaration in a static language such as C. The value assigned to this variable is convert the convert function to the declared type:

    julia> function foo()
             x::Int8 = 1000
             x
           end
    foo (generic function with 1 method)

    julia> foo()
    -24

    julia> typeof(ans)
    Int8

This feature is used to avoid the performance trap of accidentally changing the type when assigning a variable a value.

Claims only occur in specific contexts:

    x::Int8        # a variable by itself
    local x::Int8  # in a local declaration
    x::Int8 = 10   # as the left-hand side of an assignment

and applies to the entire current scope, even before the declaration. C urrently, declaration types cannot be used for global scopes, for example in REPL, because Julia does not have a global variable that is yet stereotyped. It is important to note that in the function return statement, the first two expressions above evaluate the :: a type of assertion is not a declaration.

Abstract type

Abstract types cannot be instantiated, they organize type hierarchical relationships and facilitate programmer programming. For example, programming can be for any integer type without specifying which specific integer type.

Use abstract keyword to declare abstract types:

    abstract «name»
    abstract «name» <: «supertype»

abstract keyword introduces a new abstract type «name» The type name can be <: the existing type, indicating that the newly declared abstract type is a child of the "parent" type.

If the parent type is not specified, the parent type Any -- all objects and types are children of this abstract type. I n type theory, Any the top of the type diagram and is called the "top". J ulia also has a predefined abstract "bottom" type, which is located at the bottom of the type diagram and is called None None is opposite Any No object is an instance of None and all types None of None.

The following is a concrete example of constructing a subset of abstract types of Julia's numeric system:

    abstract Number end
    abstract Real     <: Number
    abstract AbstractFloat <: Real
    abstract Integer  <: Real
    abstract Signed   <: Integer
    abstract Unsigned <: Integer

<: The operator means "the former is a child of the latter" and declares that the right side is the direct parent type of the new declaration type on the left. It can also be used to determine whether the left side is a sub-type on the right:

    julia> Integer <: Number
    true

    julia> Integer <: FloatingPoint
    false

An important use of abstract types is to provide default implementations for specific types. To give a simple example:

  function myplus(x, y)
      x + y
  endof

The first thing to note is that the above parameter declarations are x::Any and y::Any . W hen this function is called, such as myplus(2, 5) Julia first looks for myplus type. ( For more information on multiple assigned, please refer to below.) If no function is found that is more relevant than the one above, Julia defines and compiles a myplus function based on the generic function above, with two Int variables, that is, Julia defines and compiles:

  function myplus(x::Int, y::Int)
      x + y
  end

Finally, call this specific function.

As a result, programmers can write generic functions using abstract types, which can then be called by many specific combinations of types. It is precisely because of multiple delegation that programmers can have precise control over whether to call more specific or generic functions.

It is important to note that writing a function that is oriented toward an abstract type does not result in a performance loss, because each time a function is called, it is always recompiled according to a different combination of parameters. ( However, if the parameter type is a container containing abstract types, there will be performance issues;) S ee the performance tips below. )

Bit type

Bit types are specific types, and their data is made up of bits. I ntegers and floats are bit types. The standard bit type is defined in the Julia language itself:

    bitstype 16 Float16 <: FloatingPoint
    bitstype 32 Float32 <: FloatingPoint
    bitstype 64 Float64 <: FloatingPoint

    bitstype 8  Bool <: Integer
    bitstype 32 Char <: Integer

    bitstype 8  Int8     <: Signed
    bitstype 8  Uint8    <: Unsigned
    bitstype 16 Int16    <: Signed
    bitstype 16 Uint16   <: Unsigned
    bitstype 32 Int32    <: Signed
    bitstype 32 Uint32   <: Unsigned
    bitstype 64 Int64    <: Signed
    bitstype 64 Uint64   <: Unsigned
    bitstype 128 Int128  <: Signed
    bitstype 128 Uint128 <: Unsigned

The common syntax for declaring bit types is:

    bitstype «bits» «name»
    bitstype «bits» «name» <: «supertype»

«bits» how much space the type requires to be stored, «name» name of the new type. Currently, the number of bits declared by a bit type supports only a multiply of 8, so the Boolean type is also 8 bits.

Bool Int8 and Uint8 declarations are identical and consume 8 bits of memory, but they are independent of each other.

The composite type

Composite types are also known as records, structures, or objects. A compound type is a collection of variable name domains. I t is the most commonly used custom type in Julia. I n Julia, all values are objects, but functions are not bound to the objects they operate on. When Julia overloads, choose which method to call based on the type of all parameters of the function, not just the type of the first argument (see : Method).

Use type to define composite types:

    julia> type Foo
             bar
             baz::Int
             qux::Float64
           end

Objects that build Foo

    julia> foo = Foo("Hello, world.", 23, 1.5)
    Foo("Hello, world.",23,1.5)

    julia> typeof(foo)
    Foo (constructor with 2 methods)

When a type is called like a function, it can be called a type constructor. T here are two constructors per type that are automatically generated (they are called default constructors). T he first is that when the parameters passed to the constructor do not match the field types of this type, the constructor passes its convert and converts them to the corresponding field type of the type. T he second is that when each argument passed to the constructor is the same as the field type of this type, the constructor generates the type directly. Two default constructors are automatically generated to prevent users from accidentally overwriting them when declaring other new variables.

Since there is no type of constraint bar it can be assigned an arbitrary value, but baz be able to be converted Int

    julia> Foo((), 23.5, 1)
    ERROR: InexactError()
     in Foo at no file

You can use names as a function to get all the fields of a type.

    julia> names(foo)
    3-element Array{Symbol,1}:
     :bar
     :baz
     :qux

Gets the value of the composite object domain:

    julia> foo.bar
    "Hello, world."

    julia> foo.baz
    23

    julia> foo.qux
    1.5

To modify the value of a composite object domain:

    julia> foo.qux = 2
    2.0

    julia> foo.bar = 1//2
    1//2

A compound type without a domain is a monomorphic type that can have only one instance:

    type NoFields
    end

    julia> is(NoFields(), NoFields())
    true

is is function verifies that the "two" instances of NoFields are the same. For single-state types, we'll talk more about them later.

Two backgrounds are required on how composite types are instantiated, paramesing types and methods. The construction instance is described in more detail in Constructor .

Immedible composite type

You can use immutable instead type to define immutable composite types:

    immutable Complex
      real::Float64
      imag::Float64
    end

This type is similar to other composite types, except that their instances cannot be changed. Immedible composite types have several advantages:

  • They are more efficient in some cases. Types like Complex in the Complex example above are effectively encapsulated into arrays, and sometimes the compiler avoids assigning imm changeable objects completely.
  • Does not conflict with the invariation provided by the type constructor.
  • Code with immedible objects is not easy to break into.

An immedible object can contain a variable object, such as an array, a field. T hose contained variable objects remain variable; Only immedible objects can't point to other objects in their own domains.

A useful way to understand immvable composite variables is that each instance is associated with values for a particular domain - the values of those domains tell you everything about this object. C onversely, a variable object is like a small container that may contain a variety of values, so it cannot determine the object from the value of its domain. W hen deciding whether to define a type as unchanged, ask if the values of two instances containing the same domain are considered the same, or if they change independently. If they are considered to be the same, then this type should be defined as imm changed.

Again, there are two important features of immedic types in Julia:

  • Data of immedible composite types is copied when passed (as is the case when assigned, as is the case when functions are called), and relatively, data of variable types is passed to each other by reference.
  • The domain within the imm changeable composite type cannot be changed.

For readers with a C/C?background, it's important to think carefully about why these two features are closely related. I magine that if the two attributes are separate, that is, if the data is copied at the time of transmission, and the variables within the data can be changed, it will be difficult to define the actual function of a piece of code. F or example, suppose x an argument to a function, and assume that the function changes a field in the x.isprocessed = true D epending x whether x is a value pass or a reference pass, after the function is called, x original x may not change, or it may change. To prevent this uncertainty, Julia qualifies that if the argument is passed by a value, the value of its internal domain cannot be changed.

The declared type

The above three types are closely related. They have the same characteristics:

  • explicitly declared
  • There's a name
  • There is a clear parent class
  • There can be parameters

Because of the common characteristics, these types are innombly expressed as examples of the same DataType which is one of the following types:

    julia> typeof(Real)
    DataType

    julia> typeof(Int)
    DataType

DataType be abstract or concrete. I f it is specific, it will have a given size, storage schedule, and (optional) name domain. S o a bit type is a dataType that is not zero in DataType does not have a name domain. A composite type is a DataType that may have a name domain that can also be an empty set DataType

Each specific value in this system is an instance DataType or a multigroup.

Multiple group types

The type of plural group is the type of multi-group:

    julia> typeof((1,"foo",2.5))
    (Int64,ASCIIString,Float64)

Type multigroups can be used where any type is needed:

    julia> (1,"foo",2.5) :: (Int64,String,Any)
    (1,"foo",2.5)

    julia> (1,"foo",2.5) :: (Int64,String,Float32)
    ERROR: type: typeassert: expected (Int64,String,Float32), got (Int64,ASCIIString,Float64)

If non-type appears in a type multiple group, an error is reported:

    julia> (1,"foo",2.5) :: (Int64,String,3)
    ERROR: type: typeassert: expected Type{T<:Top}, got (DataType,DataType,Int64)

Note that the type of empty () group () is itself:

    julia> typeof(())
    ()

A multigroup type is about its composition type being co-variable, and a multigroup is a sub-type of another multiple group means that the type of each element of the corresponding first multigroup is a sub-type of the corresponding element type of the second multigroup. Like what:

    julia> (Int,String) <: (Real,Any)
    true

    julia> (Int,String) <: (Real,Real)
    false

    julia> (Int,String) <: (Real,)
    false

Intuitively, it's like the type of each argument of a function must be a sub-type of the function signature (when the signature matches).

Type commons

A type common body is a special abstract type that uses Union function to declare:

    julia> IntOrString = Union(Int,String)
    Union(String,Int64)

    julia> 1 :: IntOrString
    1

    julia> "Hello!" :: IntOrString
    "Hello!"

    julia> 1.0 :: IntOrString
    ERROR: type: typeassert: expected Union(String,Int64), got Float64

Does not contain any type common body, is the "bottom" type None

    julia> Union()
    None

Abstract type None a sub-type of all other types and has no instances. Union calls with Union return the instanceless type None

The paramethy type

Julia's type system supports parameterization: types can introduce parameters so that the type declares a new type for each possible combination of parameters.

All declared types DataType can be parameterized using the same syntax. We'll discuss paramethical conformity types, paramethical abstraction types, paramethical bit types, and paramethical bit types in the following order.

Paramethy the type of composite

    abstract Pointy{T}
    type Point{T} <: Pointy{T}
      x::T
      y::T
    end

After the type parameter follows the type name, it is enclosed in parentheses:

    type Point{T}
      x::T
      y::T
    end

This declaration defines the new paramethy Point{T} which has two T of type T. T he paramethy type can be any type (or an integer, in this case we use a type). T he specific Point{Float64} is equivalent to the Point T in Point Float64 The above example actually declares a number of types: Point{Float64} Point{String} Point{Int64} on, so each is now a specific type that can be used:

    julia> Point{Float64}
    Point{Float64} (constructor with 1 method)

    julia> Point{String}
    Point{String} (constructor with 1 method)

Point itself is also a valid type object:

    julia> Point
    Point{T} (constructor with 1 method)

Point is an abstract type here that contains all concrete instances such as Point{Float64} Point{String}

    julia> Point{Float64} <: Point
    true

    julia> Point{String} <: Point
    true

Other types are not their sub-types:

    julia> Float64 <: Point
    false

    julia> String <: Point
    false

Point act as a sub-type between the specific types declared by different T-values: T

    julia> Point{Float64} <: Point{Int64}
    false

    julia> Point{Float64} <: Point{Real}
    false

This is important:

Although Float64 <: Real Point{Float64} <: Point{Real} hold!

In other words, Julia's type parameters are irrelevant. Although Point{Float64} is conceptually supposed to be an instance of Point{Real} there is a difference in the representation of the two in memory:

  • Point{Float64} can easily and effectively represent a 64-digit pair
  • Point{Real} can represent a Real of any Real instance. Because Real of Real can be any size, any structure, Point{Real} that it points to a Real object

The difference is even more pronounced in the array: Array{Float64} store 64 bit floats in a continuous memory, while Array s Real Array{Real} an Real object. The size Real object may be largeer than the 64-bit float.

The constructor describes how to customize a construction method for a composite type, but if there are no special construct declarations, there are two ways to construct a new composite object by default: one is to explicitly indicate the type parameters of the construction method, and the other is to imply the type parameters by the parameters of the object construction method.

Indicates the type parameters of the construction method:

    julia> Point{Float64}(1.0,2.0)
    Point{Float64}(1.0,2.0)

    julia> typeof(ans)
    Point{Float64} (constructor with 1 method)

The number of arguments should match the constructor:

    julia> Point{Float64}(1.0)
    ERROR: no method Point{Float64}(Float64)

    julia> Point{Float64}(1.0,2.0,3.0)
    ERROR: no method Point{Float64}(Float64, Float64, Float64)

For types with type parameters, because overloading constructors is not possible, only one default constructor is automatically generated -- this constructor accepts any arguments and converts them to the corresponding field type and assigns values

In most cases, you do Point to provide the type of Point object, which can be provided by the parameter type. Therefore, the value of T be provided:

    julia> Point(1.0,2.0)
    Point{Float64}(1.0,2.0)

    julia> typeof(ans)
    Point{Float64} (constructor with 1 method)

    julia> Point(1,2)
    Point{Int64}(1,2)

    julia> typeof(ans)
    Point{Int64} (constructor with 1 method)

In the example above, Point has the same two parameter types, so T be omitted. However, when the parameter types are different, errors are reported:

    julia> Point(1,2.5)
    ERROR: `Point{T}` has no method matching Point{T}(::Int64, ::Float64)

This situation can actually be handled, see Constructor for details.

Paramethy abstract type

Similarly, paramethy abstract types declare a collection of abstract types:

    abstract Pointy{T}

Pointy is a different abstract T for Pointy{T} T. Each instance of Pointy is a subtype of it:

    julia> Pointy{Int64} <: Pointy
    true

    julia> Pointy{1} <: Pointy
    true

Paramethy abstract types are also irrelevant:


    julia> Pointy{Float64} <: Pointy{Real}
    false

    julia> Pointy{Real} <: Pointy{Float64}
    false

It can be Point{T} is Pointy{T}

    type Point{T} <: Pointy{T}
      x::T
      y::T
    end

For each T there Point{T} Pointy{T}

    julia> Point{Float64} <: Pointy{Float64}
    true

    julia> Point{Real} <: Pointy{Real}
    true

    julia> Point{String} <: Pointy{String}
    true

They are still irrelevant:

    julia> Point{Float64} <: Pointy{Real}
    false

What's the use Pointy type Pointy? Suppose we want to construct an implementation of a coordinate point, all on the diagonal x s y, so we only need one axis:


    type DiagPoint{T} <: Pointy{T}
      x::T
    end

Point{Float64} DiagPoint{Float64} Pointy{Float64} abstract type, as are other optional types T Pointy be a public interface for its subtyles. For methods and overloads, see the next section: ref: man-methods

Sometimes you need to limit the scope of T

    abstract Pointy{T<:Real}

At this T can only be Real of Real:

    julia> Pointy{Float64}
    Pointy{Float64}

    julia> Pointy{Real}
    Pointy{Real}

    julia> Pointy{String}
    ERROR: type: Pointy: in T, expected T<:Real, got Type{String}

    julia> Pointy{1}
    ERROR: type: Pointy: in T, expected T<:Real, got Int64

The type parameters of paramethy composite types can also be restricted as well:

    type Point{T<:Real} <: Pointy{T}
      x::T
      y::T
    end

Here's how Rational immutable type is defined, which represents a score:

    immutable Rational{T<:Integer} <: Real
      num::T
      den::T
    end

Single-state type

A monomorphic type is a special type of abstract paramethy. F or each type T the instance of the abstract Type{T} is object T Here are some examples:

    julia> isa(Float64, Type{Float64})
    true

    julia> isa(Real, Type{Float64})
    false

    julia> isa(Real, Type{Real})
    true

    julia> isa(Float64, Type{Real})
    false

In other words, B A isa(A,Type{B}) true only if A and B are the same object and the object is a type. When there are no Type is only an abstract type, and all types are instances of it, including single-state types:

    julia> isa(Type{Float64},Type)
    true

    julia> isa(Float64,Type)
    true

    julia> isa(Real,Type)
    true

An instance of Type is only if the Type is a type:

    julia> isa(1,Type)
    false

    julia> isa("foo",Type)
    false

Only type objects in Julia have a single-state type.

The paramethy bit type

Bit types can be declared parameterized. For example, a pointer in Julia is defined as a bit type:

    # 32-bit system:
    bitstype 32 Ptr{T}

    # 64-bit system:
    bitstype 64 Ptr{T}

The parameter type T not used for type definition, but is an abstract label that defines a set of types with the same structure that can only be distinguished by type parameters. A lthough Ptr{Float64} Ptr{Int64} the same, they are of different types. All specific pointer types are Ptr types:

    julia> Ptr{Float64} <: Ptr
    true

    julia> Ptr{Int64} <: Ptr
    true

The type alias

Julia provides typealias mechanism to implement type alias. For example, Uint Uint32 Uint64 depending on the size of the system's pointer:

    # 32-bit system:
    julia> Uint
    Uint32

    # 64-bit system:
    julia> Uint
    Uint64

It is base/boot.jl

    if is(Int,Int64)
        typealias Uint Uint64
    else
        typealias Uint Uint32
    end

For paramethy types, typealias a simple paramethyst type name. J ulia's array type is Array{T,n} T the element type and n n value of the array dimension. For simplicity, Array{Float64} indicate only the element type without specifying the dimension:

    julia> Array{Float64,1} <: Array{Float64} <: Array
    true

``Vector````Matrix`` 对象是如下定义的:

    typealias Vector{T} Array{T,1}
    typealias Matrix{T} Array{T,2}

Type operation

In Julia, the type itself is an object to which you can use normal functions. For <: determine whether the left side is a sub-type on the right.

isa function detects whether an object belongs to a specified type:

    julia> isa(1,Int)
    true

    julia> isa(1,FloatingPoint)
    false

typeof function returns the type of argument. A type is also an object, so it also has a type:

    julia> typeof(Rational)
    DataType

    julia> typeof(Union(Real,Float64,Rational))
    DataType

    julia> typeof((Rational,None))
    (DataType,UnionType)

What is the type of type? Their type is DataType

    julia> typeof(DataType)
    DataType

    julia> typeof(UnionType)
    DataType

Readers may notice that DataType similar to an empty plural group (see above). Therefore, the type of () DataType is the type itself:

    julia> typeof(())
    ()

    julia> typeof(DataType)
    DataType

    julia> typeof(((),))
    ((),)

    julia> typeof((DataType,))
    (DataType,)

    julia> typeof(((),DataType))
    ((),DataType)

super can indicate some types of parent types. Only the declared type DataType has a parent type:

    julia> super(Float64)
    FloatingPoint

    julia> super(Number)
    Any

    julia> super(String)
    Any

    julia> super(Any)
    Any

Using super for other types of objects (or super throws a "no method" error:

    julia> super(Union(Float64,Int64))
    ERROR: `super` has no method matching super(::Type{Union(Float64,Int64)})

    julia> super(None)
    ERROR: `super` has no method matching super(::Type{None})

    julia> super((Float64,Int64))
    ERROR: `super` has no method matching super(::Type{(Float64,Int64)})