RustのAssociated Typeについて

# 自己紹介
* [@tacke_jp](https://twitter.com/tacke_jp)
* [株式会社ノハナ](https://nohana.co.jp)でエンジニアをしています
* 今は[Parse.com](https://parse.com)終了にサービスが道連れにならないように移行作業をしています
* 趣味でHaskellとかRustとか触っています
* 今日はRustを触り始めて興味を持った "Associated Type" についての話をします

---

# Associated Type (関連型) is 何?
* Traitに紐づく型パラメータ
  - Traitを実装する型に対して一意に定まる<br>(型に関連付く型 = "Associated")
* 他の言語だとHaskellやSwiftなどが対応してる 
* 概念としては Type Family (型族) に近いらしい
  - 詳しい人教えてください😭

---

# 例
グラフを表現するtraitを考える。

```rust
trait Graph<N, E> {
    fn has_edge(&self, &N, &N) -> bool;
    fn edges(&self, &N) -> Vec<E>;
}
```
([公式ドキュメント](https://doc.rust-lang.org/book/associated-types.html)より)

---

実装例

```rust
struct MyGraph;
struct Node;
struct Edge;

impl Graph<Node, Edge> for MyGraph {
  fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
    true
  }

  fn edges(&self, n: &Node) -> Vec<Edge> {
    Vec::new()
  }
}
```
---

これに対してグラフの2点間の距離を計算する関数`distance`を定義することを考える。

```rust
fn distance<N, E, G: Graph<N, E>>
  (graph: &G, start: &N, end: &N)
  -> u32 { ... }
```

## 問題点
* シグネチャの表現が冗長で誤解を招きやすい
  - 任意の`N`や`E`を取れるかのように見える
  - 実際は `impl Graph<>` 時に定義されたものしか使えない
* 型パラメータが増えたときに辛い
  - 例えば `<A, B, C, D, E, F, G: Graph<A, B, C, D, E, F>>` 😨
  - これを`Graph`を引数や戻り値として使う関数ごとに書かないといけないとなると...

---

# Associated Typeを使ってみる
```rust
trait Graph {
  type N;
  type E;

  fn has_edge(&self, &Self::N, &Self::N) -> bool;
  fn edges(&self, &Self::N) -> Vec<Self::E>;
}
```

```rust
impl Graph for MyGraph {
    type N = Node;
    type E = Edge;

    fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
        true
    }

    fn edges(&self, n: &Node) -> Vec<Edge> {
        Vec::new()
    }
}
```

---

# 関数`distance`を定義してみる

```rust
fn distance<G: Graph>
  (graph: &G, start: &G::N, end: &G::N)
  -> u32 { ... }
```
すっきり書けた🙆

---

型消去する場合はこうする。

```rust
let graph = MyGraph;
let graphs = Vec::new()
               as Vec<Graph<<N=Node, E=Edge>>;
graphs.add(graph);
```
(型消去すると `N` と `E` が一意に定まらないので、具体的に指定してやる必要がある。)

---

# メリット
* 余分な型パラメータを宣言する必要がなく可読性が高い
* 型パラメータの追加/削除などの変更に強い
  - シグネチャを書き直さなくても済む
  - ライブラリ作者がAssociated Typeにしておいてくれると使い手が楽できる

---

# 注意点
* 型を代入しなくて良くなる訳ではない
  - 関数のシグネチャで `<G: Graph>` と型パラメータを省略できるが裏では代入されている
  - `let g: Graph` という変数を宣言することは出来ない
* 型の表現力が増すわけではない
  - 同等の表現はGenericsを使っても書ける

=> あくまで型レベルでのプログラミングを楽にするための機能

---

# おまけ

Rustでこう書きたいけど書けない。

```
trait Graph {
    type N;
    type E;
    fn compare<G: Graph>(&self, other: G)
      -> bool
      where Self.N == G.N
          , Self.E == G.E;
}
```

* `error: equality constraints are not yet supported in where clauses (#20041)` と怒られる😇
  - Genericsなら`fn compare<G: Graph<N, E>>(&self, other: G) -> bool;`こう書ける

ちなみにSwiftだと書ける。そのうち対応されると良いな...

---

# おまけ(その2)

[termoshtt](http://qiita.com/termoshtt) さんにコメントをいただきました。
(ありがとうございます!)

traitを使って型レベルでイコールを作るコードを紹介いただきました。

```
trait Equal<A> {}
impl<A> Equal<A> for A {}

trait Graph {
    type N;
    type E;
    fn compare<G: Graph>(&self, other: G) -> bool
        where Self::N: Equal<G::N>,
              Self::E: Equal<G::E>;
}
```

---
# おまけ(その3)

[blackenedgold](http://qiita.com/blackenedgold)さんにもコメント頂きました。(ありがとうございます!)
`Self::N` `Self::E` を代入してしまえば良いんですね...!

```
trait Graph {
    type N;
    type E;
    fn compare<G: Graph<N = Self::N, E = Self::E>>(&self, other: G)
      -> bool;
}
```

---

# まとめ

* Genericsで書いていたところをAssociated Typeで書くと簡潔になる
* Genericsを使う際の便利な補助機能だと思って使えば良さそう
* 型かわいいよ型

---

# References

* [Associated Types - The Rust Programming Language](https://doc.rust-lang.org/book/associated-types.html)
* https://github.com/rust-lang/rfcs/blob/master/text/0195-associated-items.md
* [7.7. 型の族 - 栄光のグラスゴーHaskellコンパイルシステム利用の手引き](http://www.kotha.net/ghcguide_ja/7.8.2/type-families.html)