存在量化された型

# 存在量化された型

　Haskellでは存在量化された型というものを定義することができ、下のような「何でも突っ込める箱」を書くことができる。
　すごいね。

```Haskell
{-# LANGUAGE ExistentialQuantification #-}

{- |
>>> [ Something 1, Something "str", Something [ 1, 2, 3 ] ]
[1,"str",[1,2,3]]
-}
data Something = forall a. Show a => Something a
instance Show Something where
    show (Something a) = show a
```

## 目次

1. 存在量化された型とは？
2. 利用例
3. 二つの記法
4. まとめ

## 存在量化された型とは？

### 全称量化された型

　Haskellでは量化された型というものを定義することができる。普段使っているのは、下のような全称量化された型だろう。

```Haskell
data Identity a = Identity { runIdentity :: a }
```

　上の構文は、下の二つの関数を導入する。

```Haskell
Identity :: forall a. a -> Identity a
runIdentity :: forall a. Identity a -> a
```

　`runIdentity`は、任意の型`a`について`Identity a`から`a`を取り出すことのできる関数だ。

### 存在量化された型

　全称量化された型とは別に、存在量化された型というものを定義することができる。

```Haskell
{-# LANGUAGE ExistentialQuantification #-}
data Something = forall a. Something { runSomething :: a }
```

　上の構文は、下の二つの関数を導入すると連想できる。

```Haskell
Something :: forall a. a -> Something
runSomething :: exists a. Something -> a -- 実際には導入されない。またHaskellには`exists`のようなキーワードは存在しない
```

　`Identity`の値コンストラクタと比較すると、全称量化された型との違いは`a`が左辺にしか現れないということだけだ。しかし`runIdentity`と`runSomething`の違いはより大きいように見える。
　`runSomething`の型を「`Something`から取り出せる何らかの型`a`が存在する」と読むとわかりやすい。`Something`は何らかの型を内包していることは確実だが、いかなる型をも入れることができるので、取り出す側はどんな型が出てくるか知ることができないのだ。
　したがってHaskellでは、存在量化された型の中身を取り出す関数を表現することはできない。

## 利用例

　「何でも突っ込めるが中身を取り出すことはできない型」はどのように役立てられているのだろうか。
　一番わかりやすいのは例外型としての需要だ。

### SomeException

　`Either`で失敗を表現する関数を複数使おうと思ったときに、下のような問題にぶつかったことはないだろうか？

```Haskell
mayFailA :: Either FailureA SuccessA
mayFailB :: SuccessA -> Either FailureB SuccessB

mayFailC :: Either ? SuccessB
mayFailC = mayFailA >>= mayFailB -- あれ、Leftの型が合わない……
```

　いかにもモナドの美しいパワーを発揮できそうな場面なのに、失敗を表現する型が合わないせいでそれが叶わないのだ。
　`mayFailC`の失敗を表現する型は`mayFailA`と`mayFailB`のそれの直和であるが、これを`do`文で混ぜて使うには、`mayFailA`と`mayFailB`をラップして型を揃える必要があるのだ。
　そこで、`Control.Exception`には下のような`SomeException`が用意されている。

```Haskell
data SomeException = forall e. Exception e => SomeException e
```

　この`SomeException`は`Exception`型クラスを実装している型であれば何でも突っ込むことができる上、自身も`Exception`型クラスを実装している。そのため、`Either`で失敗を表現するときは`Left`の型をこの`SomeException`にしておき、内部では自作の例外型を`SomeException`でラップして投げるという使い方ができる。
　例外を提供するパッケージでは`SomeException`の中身によって例外ハンドラを切り替えることができる仕組みを提供しているため、例外ハンドリングをする上でも型の恩恵を損なうことはない。例外については[Haskellの例外処理事情](https://qiita.com/rinse_/items/596cc07242aef880cf81)という記事が詳しい。

```Haskell
mayFailA :: Either SomeException SuccessA
mayFailB :: SuccessA -> Either SomeException SuccessB

mayFailC :: Either SomeException SuccessB
mayFailC = mayFailA >>= mayFailB -- OK!
```

### 開かれた型

　もう一つの例では、後からコンストラクタを追加できるような型を疑似的に作ってみよう。まずは通常の方法で、JSON文字列にエンコードできる`JsonValue`型を作ってみた。

```Haskell
newtype ObjectKey = ObjectKey String
data JsonValue = IntValue Int | StringValue String | ArrayValue [ JsonValue ] | ObjectValue [ (ObjectKey, JsonValue) ]

encodeJsonValue :: JsonValue -> String
encodeJsonValue (IntValue n) = show n
encodeJsonValue (StringValue s) = show s
encodeJsonValue (ArrayValue xs) = "[" <> intercalate "," (fmap encodeJsonValue xs) <> "]"
encodeJsonValue (ObjectValue xs) = "{" <> intercalate "," (fmap encodeObjectPair xs) <> "}"
    where
    encodeObjectPair (ObjectKey key, jsonValue) = show key <> ":" <> encodeJsonValue jsonValue
```

　この`JsonValue`型は表現しうる値を全てコンストラクタで表現している。例えば`Null`を足そうと思ったらコンストラクタに変更を加えなくてはならないのだ。
　そこで、存在型を使って、表現しうる値を外部から足せるような`Json`型を考えてみる。
　まずは骨格を作る。

```Haskell
{-# LANGUAGE ExistentialQuantification #-}

class JsonEncode a where
    encodeJson :: a -> String

data Json = forall a. JsonEncode a => Json a
instance JsonEncode Json where
    encodeJson (Json a) = encodeJson a
```

　そうしたら、どのモジュールでもどのパッケージでも後から自由に肉付けをしていくことができる。

```Haskell
newtype JsonInt = JsonInt Int
instance JsonEncode JsonInt where
    encodeJson (JsonInt a) = show a

newtype JsonString = JsonString String
instance JsonEncode JsonString where
    encodeJson (JsonString a) = show a

newtype JsonArray = JsonArray [ Json ]
instance JsonEncode JsonArray where
    encodeJson (JsonArray xs) = "[" <> intercalate "," (fmap encodeJson xs) <> "]"

encodeObjectPair' :: (ObjectKey, Json) -> String
encodeObjectPair' (ObjectKey key, json) = show key <> ":" <> encodeJson json

newtype ObjectKey = ObjectKey String
newtype JsonObject = JsonObject [ (ObjectKey, Json) ]
instance JsonEncode JsonObject where
    encodeJson (JsonObject xs) = "{" <> intercalate "," (fmap encodeObjectPair' xs) <> "}"
```

　存在型`Json`は後から定義された全ての型の和集合のようになっていて、ちょうど型と値コンストラクタの関係に似たものになっている。

## 記法

　ここでは存在量化された型を書く方法を紹介する。存在量化された型は二つの方法で書くことができる。

### ExistentialQuantification

　この記事で使った記法はこちらの方法だ。
　その名の通り存在量化修飾子を書けるようになる。使うにはプラグマが必要になる。

```Haskell
{-# LANGUAGE ExistentialQuantification #-}

data Something = forall a. Something a
```

### GADTs

　こちらは使った人も多いと思う。GADTsとはGeneralized algebraic data typesの略であり、より一般化された代数的データ型を定義できるようになる拡張だ。
　これを`ExplicitForAll`拡張と組み合わせることで、GADTsで存在量化された型を表現することができる。

```Haskell
{-# LANGUAGE ExplicitForAll, GADTs #-}

data Something' where
    Something' :: forall a. a -> Something'
```

## まとめ

　1. 存在量化された型とは、直観的には「何でも突っ込める箱」である。これを使うと複数の独立した型を統合することができる。
　2. 存在量化された型の中身を知る手段はないため、実用には型クラスを使って中身の型を制限することが必要である。
　3. 実用としては、疑似的に後からコンストラクタを追加できるような型を表現したり、疑似的に型クラス(`* -> Constraint`)を型(`*`)に落とし込むようなことをすることができる。

　もう少しきちんとした説明は、[Haskell/存在量化された型](https://ja.wikibooks.org/wiki/Haskell/%E5%AD%98%E5%9C%A8%E9%87%8F%E5%8C%96%E3%81%95%E3%82%8C%E3%81%9F%E5%9E%8B)で読むことができる。