May 14, 2021 Julia
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:
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.
::
Operators can be used to attach type comments to expressions and variables in a program.
There are two reasons for this:
::
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 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 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.
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 .
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:
Complex
in the Complex example above are effectively encapsulated into arrays, and sometimes the compiler avoids assigning imm changeable objects completely.
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:
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 above three types are closely related. They have the same characteristics:
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.
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).
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
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.
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.
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
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.
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
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}
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)})