メモ(整理してない)。
型の型
任意の値?x
に対して、
@assert typeof(x) isa Type
そして、
julia> subtypes(Type)
4-element Vector{Any}:
Core.TypeofBottom
DataType
Union
UnionAll
型の包含関係
演算子(関数)<:
や>:
で定義される。
値が持つのがConcrete Type、それ以外がAbstract Typeになる。
型とその包含関係で木構造をなしている。 Concrete Typeはその木構造に置いて、必ず末端(葉)にあたり、Abstract Typeは必ず葉以外にあたる。
根にあたるのが、Any
。
したがって、
- 任意の型
T
に対して、T<:Any
。 - 任意の具象型
T, S
に対して、T<:S == false
かつS<:T == false
。
具象型かを判定するのはisabstracttype
、抽象型かを判定するのはisconcretetype
。
型の束
実は、Union{}
という型は、任意の型T
の部分型。
型は束$Types, <:$をなしており、最大元(top)がAny
、最小元(bottom)がUnion{}
になっている。
julia> Union{} <: Array <: Any
true
julia> Union{} <: Tuple <: Any
true
julia> Union{} <: Tuple{Int64, Float64} <: Any
true
julia> Union{} <: Union{Int64} <: Any
true
Union{}
の型がCore.TypeofBottom <: Type
。
上位型を求める関数はsupertype
で、再帰的にAny
まで求めるのはsupertypes
。
julia> supertypes(Int64)
(Int64, Signed, Integer, Real, Number, Any)
julia> supertypes(Vector{Int64})
(Vector{Int64}, DenseVector{Int64}, AbstractVector{Int64}, Any)
束なので、束の元$a$, $b$に対して結び(join)$a\vee b$があり、$a \le a \vee b$かつ$b \le a \vee b$が成り立つ。
joinは、型T
とS
の最小の共通の上位型になっている。これを求めるJuliaの関数は、typejoin
。
julia> typejoin(Vector{Int64}, Matrix{Float64})
Array
julia> typejoin(Int64, Int32)
Signed
julia> typejoin(Int64, Bool)
Integer
julia> typejoin(Int64, Float64)
Real
julia> typejoin(Int64, Complex{Int64})
Number
julia> typejoin(Int64, String)
Any
逆に、交わりを求めるのはtypeintersect
。
これは、T<:S
でもない限り、だいたいUnion{}
(ただしTuple
)の取り扱いは違う。
julia> typeintersect(Float64, Int32)
Union{}
julia> typeintersect(Vector{Int64}, Matrix{Float64})
Union{}
julia> typeintersect(Vector{Int64}, Array)
Vector{Int64} (alias for Array{Int64, 1})
primitive type
ビット数を指定して定義する。
ほぼ組み込み型みたいなもの。
isprimitivetype
で判定する。
内部的には特定のフラグで管理してるっぽい?
DataType
について
https://docs.julialang.org/en/v1/manual/types/#man-declared-types
primitive typeとかabstract typeとかで
- 明示的に宣言されている
- 名前がある
- 明示的な上位型がある(普通、型の宣言で何も書かないと
Any
になる) - パラメーターがあるかもしれない
これらの特徴を持つのがDataType
。
Every concrete value in the system is an instance of some
DataType
.
とあるので、任意のx
について、typeof(x) isa DataType
っぽい。
typeof(x) == T
となるT
は具体型だったので、任意の具体型T
についてT isa DataType
らしい。
Type
の部分型について
抽象型・具体型を表す型はない。
はじめに見たように、
julia> subtypes(Type)
4-element Vector{Any}:
Core.TypeofBottom
DataType
Union
UnionAll
Core.TypeofBottom
はUnion{}
の型で、これのみの型。
DataType
は既出。Any isa DataType
。
Union
は和集合。
UnionAll
はVector{T} where {T}
の様にパラメータを持ってるやつ。
(一般に)パラメーターを持つ型
ざっと上げると、
Array{T, N} where {T, N}
Tuple
Union
とかがある。
まず、Union
はただ和をとってるだけなので、他2つとちょっと違う。
一般的に、T <: S
であっても、U{T} <: U{S}
ではない。
つまり、非変的(invariant)である(共変性と反変性(計算機科学))。
これは、メモリにおける表現が異なっているかららしい。
julia> Vector{Int64} <: Vector{Any}
false
julia> Complex{Float64} <: Complex{Real}
false
だたし、Tuple
は共変的(covariant)である。
julia> Tuple{Int64} <: Tuple{Any}
true
UnionAll
julia> fieldnames(UnionAll)
(:var, :body)
julia> dump(Vector)
UnionAll
var: TypeVar
name: Symbol T
lb: Union{}
ub: Any
body: Array{T, 1} <: DenseArray{T, 1}
ref::MemoryRef{T}
size::Tuple{Int64}
julia> dump(Vector{<:Real})
UnionAll
var: TypeVar
name: Symbol #s36
lb: Union{}
ub: Real <: Number
body: Array{var"#s36"<:Real, 1} <: DenseArray{var"#s36"<:Real, 1}
ref::MemoryRef{var"#s36"}
size::Tuple{Int64}
:body
が本体で、:var
はその中の自由変数。
:var
はTypeVar
で、lb
とub
が下界と上界を表してる。
大抵の場合下界はUnion{}
。
Vector{<:Real}
の例は、Vector{var"#s36"} where {var"#s36" <: Real}
に脱糖されることがわかる。
メイン: typejoin
の実装を見る
実装は、ここ。
ここまでの話が分かれば読めるかも?
TypeVar
のとき
そのTypeVar
の上界ub
とのtypejoin
片方がもう片方の部分型のとき
上位型のほう
UnionAll
のとき
T{S} where {S}
とU
だと、typejoin{T, U}{S} where {S}
。
片方がUnion
のとき
Union
の中身のtypejoin
ともう片方とのtypejoin
。
julia> typejoin(Union{Int64, String}, Float64)
Any
両方DataType
のとき
ここまでで、残ってるのはこの場合のみ
まずinferrencebarrier
なるものが使われてる。 #44390
片方がTuple
のとき
もう片方がTuple
じゃないとAny
両方Tuple
のとき
片方がTuple
のとき(パラメーターがないとき)
ここはTuple
の特殊なケースで、Tuple{T} <: Tuple
となるために、Tuple
は実はTuple{Vararg{Any}}
になってるというやつが利いてくる。
もう片方のTuple{T...}
の中身のtypejoin
をとってそれのVararg
をパラメーターに持つTuple
を返す。
両方ただのTuple
ではないとき
パラメーターの数が同じ時は、順番にtypejoin
をとっていく。
片方が多いときは、多い分のtypejoin
のVararg
をいれる(Vararg
は0個でもOkなので)。
両方Tuple
じゃないとき
メイン?最後に残ったもの
https://github.com/JuliaLang/julia/blob/v1.11.2/base/promotion.jl#L95 から始まる
まず、大体a<:b
になるまで、b
の上位型を遡っていく。
b.name.wrapper
は本体を見つけるやつ。
例:
julia> Vector{Int64}.name.wrapper
Array
julia> ComplexF64.name.wrapper
Complex
julia> Int64.name.wrapper
Int64
次にa
の方も本体がb
と同じになるまで遡る。
if a.name === Type.body.name
はType{Int64}
みたいなやつかどうかの判定。
そのなかは、Type
になる場合のパターン。
julia> typejoin(Type{T} where {T}, Type{Int64})
Type
julia> typejoin(Type{T} where {T<:Real}, Type{Int64})
Type{T} where T<:Real
julia> typejoin(Type{T} where {T<:Real}, Type{<:Any})
Type
そのあとは、T.parameter
のtypejoin
を考えることはない。なぜなら、残ったケースではパラメータにかんしてinvariantだからそのjoinを考える必要がないため。
ここで、複数パラメーターがある型は、一つずつ追加していくのの糖衣構文。
例えば、Array{T, N}
は、Array{T}{N}
(dump(Array)
をみてもわかる。)。
それを踏まえたうえで、パラメータを比較しているところ(l.118)は、一致してたらそのパラメータを共通部分として追加する感じになっている。
言葉だとうまく書けなかったので、例:
Array{Int64, 1}
とArray{Int64, 2}
だと、最初のパラメータが一致するので、この段階でArray{Int64}{N} where {N}
になる。
一致してなかったら、制限なし(i.e., ub
がAny
、lb
がBottom
)のTypeVar
をvars
に追加してってる。
制限なしのTypeVar
をとるのは、aprimary.var
(ここでこれでできるのは前述の通りUnionAll
はTypeVar
1つずつでネストされているから)。
どちらにせよ、一つ内側の.body
を新たにaprimary
にして次のパラメータのループに移る。
で、最後に自由な変数をUnionAll
で再びまとめて終わり。
ここら辺がきいてくる例は多分これ
julia> typejoin(Vector{Int64}, Matrix{Int64})
Array{Int64}
TypeVar
が具象型なので、あまり例はないはず。
おまけ:メソッドのシグニチャ
(::Method).sig
で取れる。
julia> methods(sin)[5]
sin(::Irrational{:π})
@ Base.MathConstants mathconstants.jl:146
julia> methods(sin)[5].sig
Tuple{typeof(sin), Irrational{:π}}
見たらわかる通り、Tuple{typeof(:func), arg1_type, arg2_type, ...}
がそれ。
function f(x)::Int64
end
みたいな返り値の型は出てこない。
なぜならこれは返り値を返す直前にconvert(Int64, return_val)
する糖衣構文でしかないため。
julia> f(x::T, y::T) where {T<:Real} = 1
f (generic function with 1 methods)
julia> methods(f)[1].sig
Tuple{typeof(f), T, T} where T<:Real
のように、UnionAll
になることもある。
おわりに
複雑だけどなんとなくわかった。
今は型の類似度みたいなのがほしいが、それが<:
のようにinvariantであるかは少し議論の余地があるなと思った。
(というより、invariantでないほうが直感にあう)
Comments
Reply to this post (misskey) to leave a comment.
Reply