juliaの型とメソッドの言語仕様に関するメモ

Juliaでは、全ての型がAnyの子孫だが、具象型を継承することはできない。

したがって継承用の抽象型と実行用の具象型の区別がはっきりしている。

具象型、抽象型のいずれも他の型(ただし、isbitstrueを返す型)をパラメータとして与えることができる。

::演算子は2つの機能を持つ。

  1. 型チェックの機能 … 関数の引数で型を指定すると(例: myfunc(x::Float64))それ以外を受け取った時にTypeErrorを発生させる。
  2. 型変換の機能 … 変数のアサインをする際に指定するとconvert()を自動で呼び出す。(例: x::Int8 = 100)

抽象型のミュータビリティ

抽象型の定義はabstract 定義する型 <: 親の型で行う。例:

abstract Integer <: Real

abstractをつけないと、型チェックに使える。Integer <: RealはTrueを返す。

コンパイル時に、それぞれの具象型に対応したものを必要な分(実際に呼び出している型の分)作成するため(例: myfunc(Int)myfunc(Int64)など)引数に抽象型を指定しても性能に影響は無い。(抽象型のコンテナ型を与えた際に影響がある場合もあるらしい)

全ての型のルートには抽象型Anyがある。これはわかりやすいが、その逆のUnion{}は少しわかりづらい。これは継承関係のない2つの型を1つにまとめるために使用する。

IntOrString=Union{Int, AbstractString}

複合型

以下のように定義


type Hoge
    fuga
    piyo::Int
end

引数の型を指定しないでコンストラクタHoge()を呼び出すとconvert()が呼ばれる。

デフォルトでミュータブルだが、typeの代わりにImmutableを使用するとイミュータブルにできる。

immutable Complex
    real::Float64
    imag::Float64
end

Rustにおける参照のミュータビリティの概念はない。以下の違いは重要

Anyの下(直下かどうかはわからんが)にある重要なデータ型の一つにDataTypeがある。

ジェネリック型

型パラメータを{}で与えることで作成


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

Tに型制限を与えたい場合は後に述べる抽象コンテナ型を使用する。

{}で実際の方を指定して作成するTは抽象型であってもよい。例:

Point{AbstractString}

// デフォルトコンストラクタを呼び出す。
Point{Float64}(12.0, 24.5)

以下の違いは、メモリ上でのデータの保持の仕方に起因する。

Array{Float64}はベクトル化されるが、Array{Real}はされない。おそらく後者はヒープ上にallocateされる。

よって、コンテナ型を引数に取る関数を書きたい時、そのコンテナ型の中身の型が一定の条件を満たしていることを保証したければ、以下のように書く。


function norm(T<:Real)(p::Point{T})
    sqrt(p.x^2 + p.y^2)
end

Rustならばトレイト境界を使用するところだが、Juliaはあくまで抽象型のサブタイプであるか否かをチェックする。

abstractキーワードを用いることで、コンテナ型の抽象型を上位に用意しておくこともできる。

abstract Pointy{T}した上でPointの定義をこう変える。


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

Point{Float64} <: Point{Real}Pointy{Float64} <: Pointy{Real}はfalseのままだが、Point{Float64} <: Pointy{Float64}はtrueを返すようになる。

こうしておくことで、Pointyのサブクラスが統一されたインターフェイスを持つことを保証して置けるようになる。

たとえばサブクラスが、その要素として実数を持つことを保証したい場合

abstract Pointy{T<:Real}

サブクラスがジェネリックならば、サブクラスの定義にも書いておく必要がある。


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

juliaにおける有理数(Rational)型の実装は今までに述べた機構を用いている。


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

タプル

イミュータブルなジェネリック構造体(以下)に似ている。python、Rustと同様()で作成できる。


immutable MyTuple{A, B}
    a::A
    b::B
end

しかしながらこのような構造体と違う点もある。

  1. 引数の数は任意(上だと2つしか受け取れない。)
  2. Tuple{Int} <: Tuple{Any}はtrueを返す。
  3. フィールドに名前がない。
  4. コンストラクタへの最後の引数はVarargという特殊な型になることができる。

Vararg{T}は1つ以上のTとマッチする。これは可変長引数として使用する場合に使えるらしいが、どう使えばいいのかはよくわからない。

シングルトン型

シングルトン型自体は、インスタンスが必ずシングルトンになる型全般をさすが、Juliaにおいては型オブジェクトしかない。

型オブジェクト

型の名前自体もTypeという型に属する。つまりisa(Float64, Type)はtrueをかえす。

型変換とパラメータ型を受け取るメソッドについて説明するまで、このメリットはわからない。が、多分引数の型に応じて関数が行う処理をより柔軟に変更したい際に使える。

型の検査に使える関数群

Nullable型

RustでいうOptionに相当。中身が空でもOK

中身のアンパックはRustのようにmatchではなく、get()関数を使用する。

関数

代替pythonと一緒で助かる。

オペレータ

関数として使うことができる。

+(1,2,3) … 6

同名で使える分pythonより優れている(pythonの場合、+をオーバーライドするにaは__add__()を実装しなくてはならない)が、以下のオペレータはpython同様別名の関数が呼び出される。

可変長引数関数

引数のあとに…をつける。pythonと違って引数のアンパックはされず、常にtupleになる


bar(a,b, x...) = (a, b, x)
bar(1,2,3,4,5,6) // (1, 2, (3, 4, 5, 6))を返す

ただし、関数に渡す際はアンパックしなくてはならない。これも...で行う。(pythonにおける*と同じ役割を果たすと思えば良い)


x = [3, 4]
bar(1, 2, x...)

コンストラクタ

普通は実装する必要がないが、再帰的データ構造(連結リストやバイナリツリーなど)の場合二次的に呼ばれる関数を実装し直す必要がある。このようなコンストラクタをアウターコンストラクタメソッドと呼ぶ。

インナーコンストラクタ

デフォルトコンストラクタの簡単な拡張。違いは以下

  1. 通常のメソッドと違い、構造体ブロックの中で定義する。
  2. new()という関数を使用する。

変数の条件チェックなどに使用する。例:


type OrderdPair
    x::Real
    y::Real

    OrderdPair(x,y) => x > y ? error("x should not be grater than y") : new(x, y)
end

インナーコンストラクタがあまりに複雑になった場合、アウターコンストラクタに切り替えることを考えたほうが良い。

再帰コンストラクタ

以下のようにコンストラクタ中でフィールドに値をセットする。


type SelfReferential
    obj::SelfReferential

    SelfReferential() = (x = new(); x.obj = x)

Tweet This Page
BTC address: 16BQGsTmsKtbMMT2Zwj4qNZnnAncnVCtWo
LTC address: LZuEiJecMZFN48k6jRhoRQZvH8VS1MBuGc