特別な型#

Tuple 型・NamedTuple 型#

タプル (Tuple) とは, 複数の値の組み合わせを指し, () の中に, 値をカンマ区切りで記述する.

julia> typeof((1, 2))
Tuple{Int64, Int64}

julia> typeof((1, 99.9, :a))
Tuple{Int64, Float64, Symbol}

julia> typeof((1,))
Tuple{Int64}

julia> typeof(())
Tuple{}

Tuple 型は型パラメータ数が可変なパラメトリック型と見做せる. ここで, 要素が1つだけのタプルは (1,) のようにカンマも記述することに注意. カンマを記述せずに要素を1つだけにすると, typeof((1)) === Int64 となり, だた括弧 () で括っただけとなる. また, 型パラメータを指定しない Tuple と空の {} を指定する Tuple{} は別物である.

julia> Tuple === Tuple{}
false

また, 同じ型が並ぶタプルを表す型として, NTuple{N, T} が存在する.

julia> (1, 2) isa NTuple{2, Int}
true

julia> (1, 2.0) isa NTuple{2, Int}
false

TupleTuple{} についての検証を以下に示す.

julia> isabstracttype(Tuple)
false

julia> isconcretetype(Tuple)
false

julia> isconcretetype(Tuple{})
true

julia> isconcretetype(Tuple{Int})
true

julia> typeof(Tuple)
DataType

julia> typeof(Tuple{Int})
DataType

julia> supertype(Tuple)
Any

julia> subtypes(Tuple)
Type[]

julia> Tuple{Int} <: Tuple
true

julia> Tuple{Int, Int} <: Tuple{Int}
false

julia> Tuple{Int} <: Tuple{Integer} <: Tuple{Any}
true

julia> Tuple{Int64, Float64, Symbol} <: NTuple{3, Any}
true

上記の例より, Tuple は抽象型でもなく具象型でもないが, 型パラメータを指定すると具象型となる. また, 空の型パラメータを指定した Tuple{} は具象型となる. さらに, Tuple 型の公称的基本型は Any , 公称的派生型は存在せず, 型パラメータを指定した Tuple 型は, 型パラメータを指定していない Tuple 型の派生型となる.

Tuple{Int, Int} <: Tuple{Int}false より, 2つの Int を要素として持つタプルは, 1つの Int を要素として持つタプルの派生型ではないことが分かる. 具体的には, (1, 2)(1,) の間にサブタイピング関係はないということである. また, Tuple{Int} <: Tuple{Integer} <: Tuple{Any}true である. パラメトリック型では, 型パラメータが異なれば, サブタイピングの関係にあるかどうかに関わらず型として互換性のない別物になる (不変). 例としては, Array{Int} <: Array{Integer}false . しかし, Tuple ではこれが当てはまらず, A <: Btrue / false なら Tuple{A} <: Tuple{B}true / false となる. このように, 型パラメータの基本型-派生型の関係がそのまま型の基本型-派生型の関係に反映される性質のことを共変という.

すなわち, Tuple 型は要素数を重視する型であるといえる.

julia> Tuple{Int64, Symbol, Bool} <: NTuple{3, Any}
true

上記の例より, 各要素が全く互換性のない3つの型からなるタプルは, NTuple{3, Any} の派生型, つまり要素の型に制約はないが要素数は3であるタプルということである.

julia> nt = (a=1, b=2.1, c=:OK)
(a = 1, b = 2.1, c = :OK)

julia> typeof(nt)
NamedTuple{(:a, :b, :c), Tuple{Int64, Float64, Symbol}}

julia> nt1 = (a=1,)
(a = 1,)

julia> nt2 = (;a=1)
(a = 1,)

julia> typeof(nt1)
NamedTuple{(:a,), Tuple{Int64}}

julia> nt0 = (;)
NamedTuple()

julia> typeof(nt0)
NamedTuple{(), Tuple{}}

上記の例のように, (a=1, b=2.1, c=:OK) として各要素に名前を付けたタプルを名前付きタプルといい, その型を NamedTuple 型という.

julia> (1, 2.1, :OK) isa Tuple{Int64, Float64, Symbol}
true

また, 上記の例より分かる通り, NamedTuple 型は, Tuple 型をラップして各要素に名前でアクセスできるようにした型である. さらに, 名前付きタプルは以下のように @NamedTuple マクロを用いて簡単に記述することができる.

julia> @NamedTuple begin
        a::Int
        b::Float64
        c::Symbol
       end
NamedTuple{(:a, :b, :c), Tuple{Int64, Float64, Symbol}}

julia> @NamedTuple{a::Int, b::Float64, c::Symbol}
NamedTuple{(:a, :b, :c), Tuple{Int64, Float64, Symbol}}

julia> @NamedTuple{a, b}
NamedTuple{(:a, :b), Tuple{Any, Any}}

julia> @NamedTuple{}
NamedTuple{(), Tuple{}}

NamedTuple についての検証を以下に示す.

julia> supertype(NamedTuple)
Any

julia> subtypes(NamedTuple)
Type[]

julia> isabstracttype(NamedTuple)
false

julia> isconcretetype(NamedTuple)
false

julia> isconcretetype(NamedTuple{})
false

julia> isconcretetype(NamedTuple{Int})
false

julia> typeof(NamedTuple)
UnionAll

julia> typeof(NamedTuple{})
UnionAll

julia> typeof(@NamedTuple{})
DataType

julia> @NamedTuple{a::Int, b::Float64, c::Symbol} <: @NamedTuple{a, b, c}
false

NamedTuple は抽象型でもなく具象型でもない. また, NamedTuple の公称的基本型は Any であり, 公称的派生型は存在しない. なお, Tuple に見られた共変は NamedTuple にはない.

Union 型#

型パラメータに指定した型の和集合を表す型を Union 型という.

julia> Union{Int, String}
Union{Int64, String}

julia> Union{Int, Float64, String}
Union{Float64, Int64, String}

julia> Union{Int, Integer}
Integer

julia> Union{Int, Int} === Union{Int} === Int
true

上記の例より, サブタイピング関係にない複数の型が指定された場合, 単純にそれらの和集合となり, サブタイピング関係がある複数の型が指定された場合, Any に近い方の基本型だけが残る. 例として, A <: B ならば Union{A, B} === B となる.

julia> isabstracttype(Union{Int, String})
false

julia> isconcretetype(Union{Int, String})
false

julia> supertype(Union{Int, String})
ERROR: MethodError: no method matching supertype(::Type{Union{Int64, String}})

Closest candidates are:
  supertype(::DataType)
   @ Base operators.jl:43
  supertype(::UnionAll)
   @ Base operators.jl:44

Stacktrace:
 [1] top-level scope
   @ REPL[63]:1

julia> subtypes(Union{Int, String})
Type[]

julia> Union{Int, String} <: Any
true

julia> Int <: Union{Int, String}
true

julia> String <: Union{Int, String}
true

julia> Float64 <: Union{Int, String}
false

julia> typeof(Union{Int, String})
Union

上記の例より, Union は抽象型でもなく具象型でもない. また, 公称的派生型は存在しない. なお, supertype(Union{Int, String}) でエラーが発生したのは, supertype() が公称的基本型を返す関数であるためである. 具体的には, supertype() は型定義時に struct X <: Y end 等のように定義した場合に, supertype(X) === Y となるものを返すが, そのように定義したものは DataTypeUnionAll のいずれかしか存在しないため, エラーが発生したのである.

ボトム型#

julia> Union{}
Union{}

julia> typeof(Union{})
Core.TypeofBottom

julia> Union{} <: Int
true

julia> all(Union{} <: T for T in (Float64, String, Number, Any))
true

上記の例より, Union{}Core.TypeofBottom のインスタンスであることが分かる. また, Union{} は全ての型の派生型である. ゆえに, ボトム型という. (対照的に, 全ての型の基本型である Any はトップ型ともいう. )

シングルトン型#

ある型 T に対して a isa T かつ b isa T であるような a, b がある際に必ず a == b つまり ab がオブジェクトレベルで同一レベルとなるなら, a(b) は型 T のシングルトンであるという. また, この時の型 T をシングルトン型という.

julia> struct MySingleton end

julia> a = MySingleton()
MySingleton()

julia> b = MySingleton()
MySingleton()

julia> Base.issingletontype(MySingleton)
true

上記の例より, フィールドのない構造体を定義すると, それはシングルトン型となり, そのインスタンスは一意に定まっていることが分かる. また, シングルトン型かどうかを確認する関数として Base.issingleton() が存在する.

パラメトリック型のシングルトン型は以下のようになる.

julia> Base.issingletontype(ParametricSingleton{})
false

julia> Base.issingletontype(ParametricSingleton{Int})
true

julia> all((ParametricSingleton{T}() === ParametricSingleton{T}() for T in (Int, Float64, String)))
true

上記の例より, 型パラメータが指定されていない場合はシングルトン型ではないが, 型パラメータが指定されている場合はシングルトン型であることが分かる. また, 型パラメータを指定したもののインスタンスは一意に定まる.

Julia には標準で定義されているシングルトン型も存在する.

julia> Base.issingletontype(Nothing)
true

julia> Nothing() === nothing
true

julia> Base.issingletontype(Missing)
true

julia> Missing() === missing
true

Type{T} 型セレクタ#

ある型 T に対して typeof(T) とすると, DataType, UnionAll, Union のいずれかが返ってくる. これらは, Type の派生型である.

julia> subtypes(Type)
4-element Vector{Any}:
 Core.TypeofBottom
 DataType
 Union
 UnionAll
julia> typeof(Type)
UnionAll

julia> typeof(Type{Int})
DataType

julia> Int isa Type{Int}
true

julia> Float64 isa Type{Int}
false

julia> all(T isa Type{T} for T in (Float64, String, Any))
true

julia> all(T isa Type{<:Real} for T in (Int, Float64, Signed, AbstractFloat))
true

Type はパラメトリック型であり, 型パラメータを指定していない場合 UnionAll, 型パラメータを指定している場合 DataType となる. また, 型 T を型パラメータに指定すると, T isa Type{T}true になるが, 型パラメータとして指定した型と T の型が異なれば, T isa Type{T}false となる. ただし, 型制約を指定した場合, 例として A <: B ならば A isa Type{<:B}true となる. このような Type{T} を型 T の型セレクタという.

型エイリアス#

型には別名を付けることができ, これを, 型エイリアスという. 型エイリアスの例を以下に示す.

julia> typeof(1.0 + 1.0im)
ComplexF64 (alias for Complex{Float64})

julia> typeof([1. 2.; 3. 4.])
Matrix{Float64} (alias for Array{Float64, 2})

julia> Vector
Vector (alias for Array{T, 1} where T)

なお, 型エイリアスはユーザ定義可能である.

julia> struct ParametricSingleton{T} end

julia> const IntParametricSingleton = ParametricSingleton{Int}
IntParametricSingleton (alias for ParametricSingleton{Int64})

julia> const IntegerParametricSingleton{T<:Integer} = ParametricSingleton{T}
IntegerParametricSingleton (alias for ParametricSingleton{T} where T<:Integer)

上記の例のように, const を用いて定数として定義する.

参考文献#

  • 後藤俊介, "実践Julia入門", 技術評論社, 2023. ISBN: 978-4-297-13350-4.