6 Julia type

6 Julia type

Types of

There is no class in Julia, and there is no inheritance relationship of subtypes. All concrete types are final , and only abstract types can be used as their supertypes. Inheritance in Julia is an inheritance behavior, not an inheritance structure.

Type declaration

Declaring the type of a variable can also be used to assert whether the variable type is correct

(2+4)::Float64
>> ERROR: ...
(2+4)::Int64
6

The two commonly used types of type declarations are the parameter types and return types in functions

function f9(x::Int64)::Float64
    x/10
end
f9(10)
>>1.0

Abstract type

Abstract types cannot be instantiated. The Int64/Float64 we mentioned earlier are all concrete types of abstract types. Abstract types cannot be used directly, similar to abstract classes in C++. The definition method of abstract type is as follows: Julia abstract type «name» end abstract type «name» <: «supertype» end The abstract type in the second line <:indicates that the newly declared abstract type is a subtype of the following type. If no parent type is given, the default is Any. The abstract types are:

abstract type Number end
abstract type Real <: Number end
abstract type AbstractFloat <: Real end
abstract type Integer <: Real end
abstract type Signed <: Integer end
abstract type Unsigned <: Integer end
Integer <: Number
>>true
Integer <: AbstractFloat
>>false

Primitive type

The primitive type is a concrete type, and its data is composed of simple bits. That is, Float64/Int64/Uint64/Uint32 and so on that we are talking about. It can be instantiated as an object. The syntax for declaring primitive types is:

primitive type «name» «bits» end
primitive type «name» <: «supertype» «bits» end

The standard primitive types are all defined in the language itself:

primitive type Float16 <: AbstractFloat 16 end
primitive type Float32 <: AbstractFloat 32 end
primitive type Float64 <: AbstractFloat 64 end
primitive type Bool <: Integer 8 end
primitive type Char <: AbstractChar 32 end
primitive type Int8 <: Signed 8 end
primitive type UInt8 <: Unsigned 8 end

Primitive type of application

function f10(x::Int64)::Int32
    x + 10
end
a = f10(10)

We mentioned earlier that abstract types cannot be instantiated, but if we replace the above Int64 with the abstract type Real, we find that it can be assigned correctly.

function f11(x::Real)::Real
    x + 10
end
b = f11(10)

what about this? We can use the typeof()function to view the type of the variable

typeof(a)
>>Int32
typeof(b)
>>Int64

That is, when using abstract types, Julia will recompile the concrete types of each parameter that calls it. There are two points to explain:

  • Even if our parameter type is an abstract type, there will be no loss in performance; but if the function parameter is an abstract type container (such as an array, matrix, etc.), there may be performance problems
  • The Bool, UInt8 and Int8 we mentioned earlier are all 8 bits, but their fundamental difference is that they have different super types: Bool's super type is Integer, Int8 is Signed and UInt8 is Unsigned.

Type conversion

We have already mentioned this before when we talked about functions

function foo(a, b)
    x::Int8 = a
    y::Int8 = b
    x + y
end

In this function, the Int64 type is converted to Int8 before calculation. It can also be used like the forced type conversion in C++

Int8(10)
convert(Int8, 10)

But this kind of forced conversion is limited to numbers, and can't cross the boundary

Int8(1000)
>>error
Int8("10")
>>error
convert(Int8, "10")
>>error

If we just want to convert a string into a number, we can use a parsefunction

parse(Int8, "10")

We can also convert()add a method to the function

Base.convert(::Type{Int8}, x::String) = parse(Int8, x)
convert(Int8, "10")

Type promotion

Let's look at a simple example

1 + 2.0
>>3.0

In this example, the Int64 type 1 is converted to Float64, and then the addition operation is performed

Let's look at the following example

a = (1, 2.0, Int8(3))
>>(1, 2.0, 3)
b = promote(1, 2.0, Int8(3))
>>(1.0, 2.0, 3.0)

After using promote, all elements in Tuple are upgraded to Float64 type

+(1, 2.0)
>>3.0
a = (1, 2.0)
+(a...)
>>3.0

In fact, +(1,2.0)it is equivalent +(promote(1, 2.0)...)to running and @edit +(1+2.0)viewing Julia's implementation.

Compound type

That is struct, the custom type, the keyword is , there is no class keyword in Julia, and struct is used instead

struct Foo
    x1
    x2::Int
    x3::Float64
end
foo = Foo("Hello World!", 10, 11.9)
typeof(foo)
>>Foo
foo.x1
>>"Hello World!"

During the creation of foo, two default constructors will be automatically generated. One can accept arbitrary parameters, such as x1 above, and the other accepts parameters that exactly match the field type, such as x2, x3 above.

Struct is an immutable type, and the content of foo cannot be changed after it is created.

foo.x2 = 2
>>type Foo is immutable

Stacktrace:
 [1] setproperty!(::Foo, ::Symbol, ::Int64) at .\sysimg.jl:19
 [2] top-level scope at In[15]:1

Variable composite type

mutable struct Foo
    x1
    x2::Int
    x3::Float64
end
foo2 = Foo("Hello World", 10, 12.1)
foo2.x2 = 20

The variable composite type is built on the heap and has a stable memory address.

DataType

The abstract types, primitive types and composite types we discussed above all have the following commonalities:

  • They are all declared explicitly.
  • They all have names.
  • They have all explicitly declared supertypes.
  • They can have parameters.
typeof(Real)
>>DataType

DataType can be abstract or concrete. If it is specific, it has the specified size, storage layout, and field name (optional). Therefore, the original type is a DataType with a non-zero size, but no field names. A composite type is a DataType that has a field name or is empty (zero size).

Each specific value is an instance of a certain DataType in the system.

Type Unions

Type union is a special abstract type, and its specific usage:

IntOrString = Union{Int,AbstractString}
123::IntOrString
>>123
"abc":IntOrString
>>"abc"
12.4::IntOrString
>>TypeError: in typeassert, expected Union{Int64, AbstractString}, got Float64

f(x::IntOrString) = println(x)
f(12)
f("abc")
f(23.4)
f('a')

Parameter Type

An important feature of the Julia type system is that types can support parameterization. The primitive types, abstract types, and composite types we mentioned earlier all support parameterization. Similar to the template in C++, but Julia is a dynamic language and has more obvious advantages in using parameter types.

  1. Compound parameter type
struct Pos{T}
    x::T
    y::T
end
a = Pos{Int64}(1,2)
b = Pos{Float64}(1.1,2.2)
c = Pos(3,4)
d = Pos(3.1,4.2)
typeof(c)
>>Pos{Int64}
typeof(d)
>>Pos{Float64}

Each instance is a subtype of Pos

Pos{Float64} <: Pos
>>true
Pos{Int64} <: Pos
>>true

But the concrete types declared by different T cannot be subtypes of each other

Pos{Float64} <: Pos{Real}
>>false

One more thing to note, although there are FLoat64<:Real, but not Pos{Float64}<:Pos{Real}. Their relationship is as follows

image

Therefore, the following definition cannot be used

function norm(p::Pos{Real})
    sqrt(px^2 + py^2)
end
p1 = Pos{Real}(1,2)
norm(p1)
>>2.23606797749979
p2 = Pos{Int64}(1,2)
norm(p2)
>>error

But we can write it like this

function nrom2(p::Pos{<:Real})
    sqrt(px^2 + py^2)
end
norm(p2)
>>2.23606797749979

Compound parameter types also support multiple parameter types

struct Pos1{T1,T2}
    x1::T1
    x2::T2
end
p1 = Pos1{Int64,Float64}(1,2.2)
p1.x1
>>1
p1.x2
>>2.2
  1. Abstract parameter type

From the abstract type, as the name suggests, is to add a parameter to the abstract type

abstract type Pointy{T} end 

Like compound parameter types, each instance is a subtype of Pointy

Pointy{Int64} <: Pointy
>>true

Different Ts cannot be subtypes of each other

Pointy{Float64} <: Pointy{Real}
>>false
  1. Primitive parameter type

Coming from the primitive type, is to add a parameter to the primitive type

primitive type Ptr{T} 64 end
Ptr{Float64} <: Ptr
>>true
  1. Tuple type

The definition of tuples is mentioned in the section "Variables", the content of tuples cannot be changed

t1 = (1, 2.8)

It is equivalent to the struct of the immutable parameter type

struct Tuple2{T1,T2}
    x1::T1
    x2::T2
end
t2 = Tuple2{Int64,Float64}(1, 2.8)

Is there any difference between them?

  • The tuple type can have any number of parameters.
  • The parameters of the tuple type are covariant: Tuple{Int} is a subtype of Tuple{Any}. Therefore, Tuple{Any} is considered an abstract type, and tuple types are concrete types only when their parameters are concrete types.
  • Tuples have no field names; fields can only be accessed through indexes.

Variable parameter tuple type

The last parameter of the tuple type can be a special type Vararg, which means that it can be followed by any number of parameters

Tup = Tuple{Float64, Vararg{Int64}}
isa((2.2,), Tup)
>>true
isa((2.2, 3), Tup)
>>true
ias((2.2, 3.1), Tup)
>>false
ias((2.2, 3, 4), Tup)
>>true

Monomorphic type

The so-called singleton type is Type{T}that it is an abstract type, and the only instance is the object T.

isa(Int64, Type{Int64})
>>true
isa(Real, Type{Int64})
>>flase
isa(Type{Int64}, Type)
>>true
isa(Type{Real}, Type)
>>true

UnionAll type

In the abstract parameter type, we mentioned that Pointyit is Pointy{T})the super type of all its instances , but the type of T is uncertain, so how does this work? This introduces the UnionAll type. Pointy is a UnionAll type, an iterative union of all values ​​of certain parameters of this type.

UnionAll types are usually wherewritten using keywords

struct Pos{T}
    x::T
    y::T
end
function f1(p::Pos{T} where T<:Real)
    p
end

When there are two type parameters

struct Pos2{T1,T2}
    x::T1
    y::T2
end
function f2(p::Pos2{T1,T2} where T1<:Int64 where T2<:Float64)
    p
end

where can also limit the upper and lower bounds of T

function f23(Pos{T} where Int64<:T<:Real)
    p
end
Reference: https://cloud.tencent.com/developer/article/1652990 6 Julia Type-Cloud + Community-Tencent Cloud