Julia 0.6-dev の新しい Type System に触れてみた。

# 前置き

色々やりたいことはあるのに思うように捗りません[^tensorflow_rb]。
そんなときは現実逃避のために、Julia の最新開発版をインストールして新機能を試してみて近未来を覗いたりします[^jl05dev]。

v0.6.0-dev に、前々から少し期待していた「型システムの仕様変更」がついに入りました！
割と自分の思っていた方向に実装がされていたので、紹介したいと思います。

[^tensorflow_rb]: [名古屋Ruby会議03](https://regional.rubykaigi.org/nagoya03/) に向けて [tensorflow.rb](https://github.com/somaticio/tensorflow.rb) を試そうと思ったのですがインストールはできたのに`Session.new`で segmentation fault …うがー
[^jl05dev]: 過去にも勉強が捗らないときに「[Julia 0.5-dev の Generator に触れてみた。](http://qiita.com/antimon2/items/89562cf7c1115c500f02)」という記事を書いています。

【2018/02/19 11:45】編集：参考リンク集のリンク先を一部修正[^old_link]。

[^old_link]: NEWS.md とか manual とかはどんどん新しい情報に書き換わるので、そこにリンクしておいた箇所はたまにこういうメンテ（＝正しいリンク先（＝古い情報が残っているページ）へリンクを変更）する必要がありますよね。気付いたらコメントなり編集リクエスト欲しい（今回は自分で気付いた）。

# Julia の型システム概要

Julia は、動的型付言語です。コンパイル時ではなく基本的に実行時に型が決定します。
一方で、関数の引数や戻り値等に、型アノテーションを付けることはできます。
特に関数については、引数の違い（型及び個数）で同じ関数を多重定義することができ、実行時に適切な引数を持った関数が呼ばれる仕組みを取っています（**多重ディスパッチ** と言います）。これが（おそらく）Julia の最大の特徴。

また他言語の総称型のように、**型パラメータ** を取ることのできる型を宣言することもできます。
標準では、例えば `Array` 型は2つの型パラメータを取ります。

```jlcon
julia> Array
Array{T,N}
```

`T` は要素の型、`N` は次元数（つまり型ではなく整数値）です。
例えば `Int` の1次元配列は `Array{Int,1}` 型になります。

```jlcon
julia> isa([1, 2, 3], Array{Int, 1})
true

julia> isa([1, 2, 3], Array{Int, 2})
false

julia> isa([1, 2, 3], Array{Float64, 1})
false

```

こんな感じ。要素の型・次元数どちらかが異なれば `isa()` は `false` を返します。
あとより後方の型パラメータは省略でき、省略されるとより「制限が緩くなる」感じになります。
具体的には、↓が成り立ちます。

```jlcon
julia> Array{Int,1} <: Array{Int} <: Array
true

julia> isa([1, 2, 3], Array{Int})
true

julia> isa([1, 2, 3], Array)
true

```

演算子 `<:` は、`A <: B` で「`A` が `B` の subtype（同一の型でもOK）」の時に `true` となります。`issubtype(A, B)` と書いても同じ（エイリアス）です。

また例えば関数の引数の型やその型パラメータに、**型制約** を付けることも可能です。

```jlcon
julia> function hoge{T<:Integer}(a::Array{T})
           :ok
       end
hoge (generic function with 1 method)

julia> hoge([1, 2, 3])
:ok

julia> hoge([1, big"2", UInt8(3)])
:ok

julia> hoge([0.1, 0.2, 0.3])
ERROR: MethodError: no method matching hoge(::Array{Float64,1})
Closest candidates are:
  hoge{T<:Integer}(::Array{T<:Integer,N}) at REPL[XX]:2

```

この例は引数を `a::Array{T}` とし、関数名と`()`の間に `{T<:Integer}` と書く[^t_issubtype_integer]ことで、「引数は『要素が（何らかの）整数型である配列』」という意味になり、そうであれば関数が実行され、そうでなければ「マッチするメソッドがない」というエラーになっています。 

[^t_issubtype_integer]: この場合の `<:` は `Bool` を返す演算子ではなく、「`T`は`Integer`の subtype」ということを表すだけの文法的な記述です。またこのときの `T` は、宣言時においては特定の型を表すものではなく `TypeVar` と呼ばれるものになります。

Julia の型システムの基本について、少し詳しいことが、2016年の [JuliaAdventCalendar](http://qiita.com/advent-calendar/2016/julialang) で紹介されています（「[Juliaのtype systemの基本についてざっくりと](http://qiita.com/KeitaNakamura/items/0e55cfaf295984537fcf)」）。こちらも参照してください。

# v0.6 で追加される（た）新しい型関連の機能・文法

Julia v0.6 の型システムの仕様変更は、主に先述の 型パラメータ（型制約） に関するものとなります。
簡単にまとめると、以下の2点：

+ 型制約をつけるときに、同じ `{～}` 内で前方で宣言された型パラメータを参照できるようになった。
+ `where` を用いた型制約の記述（例：`Array{T} where T <: Integer`）ができるようになった。

順に説明していきます。

## 型パラメータの（後方）参照[^backward_ref]

[^backward_ref]: 普通「後方参照」という用語は、正規表現においてマッチした部分文字列をそのあとで番号等で参照（例：`r"([abc])n\1"` は `"ana"` にマッチ）することを指す言葉です。なので厳密には同じ概念ではありませんが、言葉として便利なのでこの記事では便宜的に「（後方）参照」という言葉を使います。もし正しい用語とか「こう言い換えられるよ」という Suggest があればコメントor編集リクエストください。

これが欲しかった！
関数宣言時や型定義時に、同じ `{〜}` 内で前方で宣言した型パラメータを再利用することができるようになります（ました）。なぜ今までできなかった！

例えば、こんな風に書けます。

```jl
type Hoge{T<:Integer, A<:AbstractArray{T}}
    x::T
    a::A
end
```

つまり。
ある整数型の値 `x` と、それと同じ型を要素として持つある配列型の値 `a` をメンバとして持つ型を定義しています。

ごくごく自然な記述ですよね？
なのにこれを julia v0.5.0 以前で実行しようとすると `ERROR: UndefVarError: T not defined` のようなエラーになります[^err_msg_per_ver]。何度これで「うがー」と唸ったことか。

[^err_msg_per_ver]: エラーの種類や内容はバージョンによって異なりますが、意味としては大体同じエラーになるはず。

### 用例1：型パラメータのネスト

何が嬉しいかって、「型パラメータのネスト」が扱いやすくなる（なった）んです。

こんな例を考えてみましょう。
まずは、型パラメータを持つ型を階層構造で定義します。

```jl
# v0.5.0, v0.6.0 共通
abstract AbstractTypeParam{T}

type TypeParam1{T} <: AbstractTypeParam{T}
    t::T
end
type TypeParam2{T} <: AbstractTypeParam{T}
    t::T
end
```

そして、（その型の値（インスタンス）ではなく）「その型そのもの」を引数で受け取って、その型パラメータ（が表す型）を返してほしい、という要望があるとします。

```jlcon
julia> getparamtype(TypeParam1{Int})
Int64

julia> getparamtype(TypeParam2{String})
String
```

見るからに簡単に実装出来そうですよね？
でもどう実装すれば良いか？

julia v0.6.0 なら、たった1行で実現できます↓。

```jl
getparamtype{T,TP<:AbstractTypeParam{T}}(::Type{TP}) = T
```

一見複雑ですが、とにかくコレ1行だけ記述すれば要求を満たせます。

同じことを v0.5.0 以前でやろうとすると。
まず「同じ `{〜}` の中で、前方で宣言した `T` を参照している」ので、そのままでは先ほどと同様、`ERROR: UndefVarError: T not defined` というエラーになります。

では、↓こうは書けないか？と思うかもしれません。

```jl
getparamtype{T,TP<:AbstractTypeParam}(::Type{TP{T}}) = T
# @> ERROR: TypeError: Type{...} expression: expected Type{T}, got TypeVar
```

これは↑のようなエラーになります[^err_got_typevar]。これ、分かりにくいですが、エラーが起きているのは `TP{T}` の部分です。`TP` というのが（宣言時においてはまだ）型ではなく `TypeVar` と呼ばれるものになっており、型パラメータを取ることができないからです。

[^err_got_typevar]: ちなみにこれは v0.5.0 のエラーで、v0.6.0 では `ERROR: TypeError: Type{...} expression: expected UnionAll, got TypeVar` と、メッセージが少し異なります。

ではどうすれば良いか。
まず、↓について考えてみましょう。

```jl
getparamtype{T}(::Type{AbstractTypeParam{T}}) = T
```

これは `getparamtype(AbstractTypeParam{Int})` とした場合には正しく `Int64` が返ります。しかし `AbstractParamType` を継承した型を渡して、例えば `getparamtype(TypeParam1{Int})` を実行しようとしても、`ERROR: MethodError …` というエラーになってしまいます。
なぜなら、↓だからです。

```jlcon
julia> isa(AbstractTypeParam{Int}, Type{AbstractTypeParam{Int}})
true

julia> isa(TypeParam1{Int}, Type{AbstractTypeParam{Int}})
false

```

つまり引数の型が合わないから受け取れない（受け取れる関数が多重定義されていない）からです。
ならば、それを多重定義すれば良い。つまり取り敢えずこうすれば要求は満たせます：

```jl
getparamtype{T}(::Type{AbstractTypeParam{T}}) = T
getparamtype{T}(::Type{TypeParam1{T}}) = T
getparamtype{T}(::Type{TypeParam2{T}}) = T
```

そう、必要な型に対して関数を全部記述すれば。
……。
センスないですよね！

それに「`AbstractTypeParam` を継承した新しい型（例：`TypeParam3`）を定義したら、それに合わせて `getparamtype()` 関数も多重定義する（例：`getparamtype{T}(::Type{TypeParam3{T}}) = T`）」必要があります。
めんどくさいですよね！ 全然 DRY じゃないですよね！

じゃ、こうすれば DRY に書けます。

```jl
getparamtype{T}(::Type{AbstractTypeParam{T}}) = T
getparamtype{TP<:AbstractTypeParam}(::Type{TP}) = getparamtype(supertype(TP))
```

つまり、`AbstractTypeParam` の subtype が渡ってきたら、その supertype を取得して、再帰呼び出しする。ということ。例えばこの場合、`supertype(TypeParam1{Int}) == AbstractTypeParam{Int}` なので、最終的に `getparamtype{T}(::Type{AbstractTypeParam{T}}) = T` が呼ばれて `T(=Int64)` が返る、という仕組み。
……。
うん。やっていることは追えば分かるけれど、割とトリッキーなコードですよね。非常に見通しが悪い。
元々何がしたいんでしたっけ？ このコードを見てそれを説明できますか？

改めてやりたいこと（仕様）をもう一度、日本語（自然言語）を使って表現してみると、こうなります。

「何かしらの型 `T` があって、それを型パラメータとして持つ、ある `AbstractTypeParam` の subtype（＝`○ <: AbstractTypeParam{T}`となるような`○`）を引数に渡したときに、`T` を返す」

そこで先ほどの（v0.6.0 用の）コードを、もう一度見てみましょう。

```jl
getparamtype{T,TP<:AbstractTypeParam{T})(::Type{TP}) = T
```

ほら！ 今日本語で書いた仕様をそのまま記述できてるじゃないですか！

というように、型パラメータを持つ型をネストしたときに、その型パラメータを非常に扱いやすくなり、より直感的かつ簡潔に書けるようになる（なった）んです。ようやくなった！ なぜ今までできなかった！

### 用例2：共変

先述の通り、Julia は動的型付けなので、変数に型は存在しません（実行時には型は確定している）。
ただ関数の引数に型アノテーションを付けることはできるので、引数で受け取った値（仮引数の値）は定義時にほぼ確定します。
またその時に型パラメータ（例：`fuga{T}(x::T) = 〜`）を利用すれば、その型を `T` という定数（扱いの変数？）で受け取ることができます（先ほどの例でもそれを利用しています）。

ところで、例えば、以下のような関数を考えてみます。

```jl
fuga{T}(x::T,xs::AbstractArray{T}) = (typeof(x),typeof(xs),T)
```

これは以下のような結果になります。

```jlcon
julia> fuga(1, [2,3,4])
(Int64,Array{Int64,1},Int64)

julia> fuga0(big"1", [2,3,4])
ERROR: MethodError: no method matching fuga0(::BigInt, ::Array{Int64,1})
Closest candidates are:
  fuga{T}(::T, ::AbstractArray{T,N} where N) at REPL[78]:1

julia> fuga(1.0, [1,2,3])
ERROR: MethodError: no method matching fuga0(::Float64, ::Array{Int64,1})
Closest candidates are:
  fuga{T}(::T, ::AbstractArray{T,N} where N) at REPL[78]:1

julia> fuga(1, Integer[2,3,4])
(Int64,Array{Integer,1},Integer)

```

字面的には、「第2引数の配列の要素の型（第1型パラメータ）と、第1引数の型が一致しているとき」に限って実行されそうに見えます。実際、最初の3つの例は「`Int64`と`Int64`→○」「`Int64`と`BigInt`→×」「`Int64`と`Float64`→×」となっています。

しかし実際には、最後の例もエラーにならず実行されます。「`Integer`と`Int64`→○」です。
ただ落ち着いて考えれば、これは分かると思います。まず `Array{Integer}` という配列型は、要素の型は `Integer` なので、`T` として `Integer` が当てはまります。そうすると `Int64` は `Integer` の subtype（`Int64 <: Signed <: Integer` なので descendent type と言うべき？）なので、`x::T` という引数で `Int64` の値を受け取ることができる、ということです。
ただこのとき、`T` は「第2引数`xs`（配列）の要素の型」は直接表していても、「第1引数`x`の型」を直接は表していないことになります。実行時に型は確定していますが、関数定義時には「`x`は`T`型の subtype」という情報しかないことになります。つまり `T` と `x` の実際の型が一致するとは限りません。`typeof(x) == T` だと思い込んで `T` を利用したコードを書こうとしたときに想定外の動作をするかもしれません[^one_real]。

[^one_real]: 例えば `one(Float64)` は `1.0`（`Float64`）となりますが、`one(Real)`（`Float64 <: AbstractFloat <: Real`）は `1`（`Int64`）となります。`fuga(1.0, Real[2.0,3.0,4.0])` と言うコードは `T==Real` となるので、浮動小数だと仮定して `T` を利用したコードを書こうとするときっとハマります。

そこで、以下のような関数を考えてみましょう（Julia 0.6 以降）。

```jl
fuga2{S,T<:S}(x::T,xs::AbstractArray{S}) = (typeof(x),typeof(xs),T,S)
```

先ほどと同じ引数で実行してみると↓。

```jlcon
julia> fuga2(1, [1,2,3])
(Int64,Array{Int64,1},Int64,Int64)

julia> fuga2(big"1", [1,2,3])
ERROR: MethodError: no method matching fuga2(::BigInt, ::Array{Int64,1})
Closest candidates are:
  fuga2{S,T<:S}(::T<:S, ::AbstractArray{S,N} where N) at REPL[96]:1

julia> fuga2(1.0, [1,2,3])
ERROR: MethodError: no method matching fuga2(::Float64, ::Array{Int64,1})
Closest candidates are:
  fuga2{S,T<:S}(::T<:S, ::AbstractArray{S,N} where N) at REPL[96]:1

julia> fuga2(1, Integer[1,2,3])
(Int64,Array{Integer,1},Int64,Integer)

```

最初の3つは、動作状況も結果も `fuga` と全く同様です。
最後。`T` は引数 `x` の型（`Int64`）に一致し、`S` は引数 `xs` の配列の要素の型（`Integer`）に一致しています。
これなら安心して、`T` を信用して扱うことができますね。

と同時に、先ほど「落ち着いて考え」たことを、考えなくても「コードを見れば分かる」ようにもなっています。
「第1引数 `x` の型（`T`）は、第2引数 `xs` の要素の型（`S`）の subtype」と、関数定義のコードに記述されていますよね？
このような「コードを見ればその意図が分かる」ような記述が、実はできるようになる（なった）、ということです。

## `where` を利用した型制約の記述

型制約を記述するのに、新しい文法として以下のように記述できるようになります（ました）。

```jl
Array{T} where T <: Integer
```

関数定義時にも記述することができ、例えば前節までの例を書き換えると以下のようになります。

```jl
function hoge_(a::Array{T}) where T <: Integer
    :ok
end

getparamtype_(::Type{TP}) where TP<:AbstractTypeParam{T} where T = T

fuga2_(x::T,xs::AbstractArray{S}) where T<:S where S = (typeof(x),typeof(xs),T,S)
```

関数定義の場合は、関数名と引数列の`(`の間の `{}` に記述していた内容を、引数列の `)` の後に `where 〜` を並べる形になります（順番注意）。
なお関数定義時に型制約を付ける目的でこの記述を利用した場合は、内部的には従来通り `{〜}` 内に記述した場合と何ら変わりはない模様です。

```jlcon
julia> methods(hoge)
# 1 method for generic function "hoge":
hoge{T<:Integer}(a::Array{T,N} where N) in Main at REPL[XX]:1

julia> methods(hoge_)
# 1 method for generic function "hoge_":
hoge_{T<:Integer}(a::Array{T,N} where N) in Main at REPL[XX]:2

```

### 用例：上限境界・下限境界

[こちらの記事](http://bicycle1885.hatenablog.com/entry/2016/12/01/210618 "Julia現状確認 (言語編) - りんごがでている") にもある通り、この記法はそのまま サブタイピング に利用できます。
つまり、型そのものにある制約を設け、その「制約を持った型」と他の型との間の継承関係を作ることができます。

まず、「型制約なし」の場合。

```jlcon
julia> Array{T} where T  # 型制約なし
Array{T,N} where N where T

julia> Array{Int} <: Array{T} where T
true

julia> Array{Float64} <: Array{T} where T
true

```

これは「`Array` の（第1）型パラメータ `T` として、特に制約を設けていない」ということ。この場合、要素の型がどのような `Array` も subtype として扱えます。
これは実は、（少なくとも Julia v0.6.0 においては）`Array{Int} <: Array` と何ら変わりません。というか、（↑の例を見れば分かる通り）`Array{T} where T` は `Array{T,N} where N where T` の省略形であるのと同様に、`Array` も `Array{T} where T` の省略形（ひいては `Array{T,N} where N where T` の省略形）と見なせます。例えば `Array <: (Array{T} where T) <: Array` は `true` を返します。

この記述方法で「使える」のは、「上限境界」と「下限境界」の用例です。

#### 上限境界

**上限境界** とは、「supertype に境界を設ける」ということです。言い換えると、指定した型もしくはその subtype を許可する、という制約です。

例を示します。

```jlcon
julia> Array{T} where T <: Integer # 型制約（上限境界）あり
Array{T,N} where N where T<:Integer

julia> Array{Int} <: Array{T} where T <: Integer
true

julia> Array{Float64} <: Array{T} where T <: Integer
false

julia> Array{Real} <: Array{T} where T <: Integer
false

```

`Array{T} where T <: Integer` は「`Integer` の何らかの subtype を要素の型とする配列型」という意味になります。
`Int64 <: Integer` なので `Array{Int}` は `Array{T} where T <: Integer` の subtype となります。
一方 `Integer <: Real` であるため、最後の例は `false` になります。
また `Float64` は `Integer` と直接的な継承関係にないので、やはり `false` です。

#### 下限境界

**下限境界** は 上限境界の逆で、「subtype に境界を設ける」ということです。言い換えると、指定した型もしくはその supertype を許可する、という制約です。

例を示します。

```jlcon
julia> Array{T} where T >: Int # 型制約（下限境界）あり
Array{T,N} where N where T>:Int64

julia> Array{Int} <: Array{T} where T >: Int
true

julia> Array{Float64} <: Array{T} where T >: Int
false

julia> Array{Real} <: Array{T} where T >: Int
true

```

`Array{T} where T >: Int`[^t_issupertype_int] は「`Int` の何らかの supertype を要素の型とする配列型」という意味になります。
`Int <: Int` なので `Array{Int}` は `Array{T} where T >: Int` の subtype となります。
`Float64` は `Int` と直接的な継承関係にないので、`Array{Float64} <: Array{T} where T >: Int` やはり `false` です。
一方、`Int <: Real` であるため、最後の例は `true` になります。`Array{Any} <: Array{T} where T >: Int` も `true` です。

ちなみに、ここに出てきた `>:` は関数宣言時や型宣言時等の型制約でも利用することができて、例えば以下のように書くこともできます：

```jl
fuga2__{T,S>:T}(x::T,xs::AbstractArray{S}) = (typeof(x),typeof(xs),T,S)
```

ただしこの場合、先ほどまで（`fuga2` や `fuga2_`）と結果が少しだけ異なります。
ここでは深入りしませんが、興味のある方は実際に試してみてください。

[^t_issupertype_int]: この場合の `>:` は、先ほどの 型制約記述中の `<:`[^t_issubtype_integer] と同様、「`T`は`Int`の supertype」ということを表す文法的な記述です。また（julia v0.6.0-dev.2763 から）`>:` という演算子も追加されており、`A >: B` は `B <: A` と同じ意味になります。

あと、上限下限境界両方を指定することもできます。

```jlcon
julia> Array{T} where Int <: T <: Integer  # 型制約（上限下限境界）あり
Array{T,N} where N where Int64<:T<:Integer

```

こちらも具体的な動作例は省略します。

#### v0.5.0 以前の上限・下限境界

実は余り知られていませんが[^type_restrict_050]、v0.5.0（以前）でも、型制約（上限・下限境界）を設けることは実はできます。

[^type_restrict_050]: 私も今回調べて初めて知りました(^-^;

以下は v0.5.0 での動作です：

上限境界：

```jlcon
julia> Array{TypeVar(:T,Integer)}
Array{T<:Integer,N}

julia> Array{Int} <: Array{TypeVar(:T,Integer)}
true

julia> Array{Float64} <: Array{TypeVar(:T,Integer)}
false

julia> Array{Real} <: Array{TypeVar(:T,Integer)}
false

```

下限境界：

```jlcon
julia> Array{TypeVar(:T,Int,Any)}
Array{Int64<:T,N}

julia> Array{Int} <: Array{TypeVar(:T,Int,Any)}
true

julia> Array{Float64} <: Array{TypeVar(:T,Int,Any)}
false

julia> Array{Real} <: Array{TypeVar(:T,Int,Any)}
true

```

上限下限境界（動作例省略）：

```jlcon
julia> Array{TypeVar(:T,Int,Integer)}
Array{Int64<:T<:Integer,N}

```

軽く試したところ、v0.4.7, v0.3.12 でも同様の動作となりました。

また v0.6.0 でも、これらのコードはエラーにはなりませんでしたが、動作結果は異なるものとなりました。元々直感的な記述ではないですし、意味が大きく変わったようです。
ということで、これからは v0.6.0 以降の `where` を使った記述だけを考えるようにしましょう。

# まとめ

Julia の型システム、当初から割としっかり考えられており、一貫性のあるものでした。

v0.6.0 での新型システムは、それをほぼ互換性を保ったまま、より柔軟に、というより正確にはより直感的かつ簡潔な記述ができるように、進化した・洗練された感じです。

v1.0 の正式リリース前に、この新仕様が入れられたこと。すごく良かったと思います。
v0.6.0、そして v1.0 の正式リリースが今から楽しみ♪

# 参考

+ [New language features - Julia v0.6.0 Release Notes](https://github.com/JuliaLang/julia/blob/v0.6.0/NEWS.md#new-language-features "New language features - Julia v0.6.0 Release Notes - julia/NEWS.md at master · JuliaLang/julia")
+ [Julia Documentation](http://docs.julialang.org/) » More about types
    + [More about types (v0.5)](https://docs.julialang.org/en/release-0.5/devdocs/types/ "More about types &mdash; Julia Language 0.5.1-pre documentation")（for v0.5.x）
    + [More about types (v0.6)](https://docs.julialang.org/en/release-0.6/devdocs/types "More about types · The Julia Language")（for v0.6.0）
+ [Issues · JuliaLang/julia](https://github.com/JuliaLang/julia/issues "Issues · JuliaLang/julia")
    + [type system design iteration #8974](https://github.com/JuliaLang/julia/issues/8974 "type system design iteration · Issue #8974 · JuliaLang/julia")
    + [type system revision and new subtype algorithm #18457](https://github.com/JuliaLang/julia/pull/18457 "type system revision and new subtype algorithm by JeffBezanson · Pull Request #18457 · JuliaLang/julia")
+ [Julia Advent Calendar 2016](http://qiita.com/advent-calendar/2016/julialang "Julia Advent Calendar 2016 - Qiita")
    + 1日目：[Julia現状確認 (言語編)](http://bicycle1885.hatenablog.com/entry/2016/12/01/210618 "Julia現状確認 (言語編) - りんごがでている")
    + 7日目：[Juliaのtype systemの基本についてざっくりと](http://qiita.com/KeitaNakamura/items/0e55cfaf295984537fcf "Juliaのtype systemの基本についてざっくりと - Qiita")