独断と偏見で語るGHCのderiving系拡張

先日のHaskell Dayでderivingに関する発表があった

* [Haskell Day 2019を開催しました！](https://haskell.jp/blog/posts/2019/haskell-day-2019.html)
* aiya000, [「しんさんきぼう」のDerivingストラテジー](https://aiya000.github.io/Maid/haskell-day-2019-deriving/#/)

ので、触発されて私もderivingについて思うところを書いてみます。主にderiving系拡張の落とし穴・注意点に重点を置きます。

# 標準でderiveできるやつ、またはGHCに組み込まれているやつ (stock deriving)

標準で `Eq`, `Ord`, `Enum`, `Bounded`, `Show`, `Read`, `Data.Ix.Ix` がderiveできます。この手の話題で `Ix` はよく見落とされます。ちゅうか [Haskell 2010 Language Report, Chapter 11](https://www.haskell.org/onlinereport/haskell2010/haskellch11.html) のderive可能なクラスの一覧からもオミットされている……。

GHC拡張を有効にすることで、他のいくつかのクラスでも型の定義に応じたderivingができるようになります。

* DeriveGeneric

みんな大好き `Generic` をderiveする機能です。`Generic` クラスは手書きしないですよね。

* DeriveFunctor, DeriveFoldable, DeriveTraversable

それぞれ `Functor`, `Foldable`, `Traversable` をderiveできるようにする機能です。

* DeriveDataTypeable

`Data.Data` の `Data` クラスをderiveできるようにする機能です。元々のネーミング的には「`Data` クラスと `Typeable` クラス」だったと思われます。モジュール名 `Data.Typeable` ではなくて。

歴史的な経緯はともかく、現在のGHCではこの拡張は `Typeable` クラスとは関係がありません。DeriveDataTypeableを有効にしなくても `deriving Typeable` と書けますし、そもそもderiving節を書かなくても自動的に `Typeable` のインスタンスが用意されます。

* DeriveLift

Template Haskell関係の `Lift` クラスをderiveできるようになるらしいです。筆者はTemplate Haskellをあまり使わないのでこれ以上の解説はしません。

# DeriveAnyClass

> *この拡張を使う人はこんな拡張も使っています：DeriveGeneric*

*Since: GHC 7.10.1*

DeriveAnyClass は、where節が空のインスタンス宣言

```haskell:DeriveAnyClass不使用時
data T = ...
instance Foo T
```

をderiving節

```haskell:DeriveAnyClass使用時
data T = ...
   deriving (Foo)
```

として書ける機能です（インスタンス宣言に制約が必要な場合は適宜推論で補われます）。

典型的には、 `Generic` に基づいたデフォルト実装[^default-signatures]を持つ型クラスに使われます。 `FromJSON`, `ToJSON`, `NFData` などがそういうクラスの例です。[`NFData` クラスのドキュメント](https://hackage.haskell.org/package/deepseq/docs/Control-DeepSeq.html#t:NFData)には、まさに DeriveAnyClass と組み合わせる例が載っていますね。

[^default-signatures]: `Generic` に基づいたデフォルト実装を提供する側は DefaultSignatures 拡張を使いますが、derivingを使う側は DeriveGeneric だけで十分です。

この拡張に「Derive<strong>Any</strong>Class」という名前がついているのはいささかミスリーディングで、事実上、**まともなdefault実装を用意しているクラスにしか適用できません**（後述の「注意点」も参照）。

この拡張を好意的に紹介するなら「（DeriveGenericと組み合わせることで）stock derivingのような『型の構造に応じたderiving』を他の型クラスでも使えるようにする拡張」となるでしょう。

## DeriveAnyClassの注意点、もしくはDeriveAnyClassを避けるべき理由

明らかにデフォルト実装を持っていないクラス、例えば `Num` に対して DeriveAnyClass を使ってみましょう。

```haskell
{-# LANGUAGE DeriveAnyClass #-}

data T = T deriving (Eq, Show, Num)

main = print (T-T)
```

この「間違った」コードをコンパイルするとどうなるでしょうか？予想してみてください！

……

このコードは、なんと**コンパイルが通ります！**もちろん警告は出ます：

```
Test.hs:3:32: warning: [-Wmissing-methods]
    • No explicit implementation for
        ‘+’, ‘*’, ‘abs’, ‘signum’, ‘fromInteger’, and (either ‘negate’
                                                              or
                                                              ‘-’)
    • In the instance declaration for ‘Num T’
  |
3 | data T = T deriving (Eq, Show, Num)
  |                                ^^^
```

何が起こったかというと、**メソッドが全て `undefined` で埋められたインスタンスが生成された**のです[^error-message-missing-methods]。

[^error-message-missing-methods]: 呼び出した時のエラーメッセージはちょっと違いますが。

「メソッドが `undefined` で埋められる」のは別に DeriveAnyClass 特有の挙動ではなく、単に where 節が空のインスタンスを書くことで発生します。 DeriveAnyClass は「where節が空のインスタンス定義」の省略記法なので、当然と言えば当然ですね。

```haskell
data T = T deriving (Eq, Show)

instance Num T -- コンパイルが通る

main = print (T-T)
```

この挙動が DeriveAnyClass で特に問題になるとすれば、stock derivingに対応していないクラスをderive使用した時にGHCが「Try enabling DeriveAnyClass」みたいなサジェスチョンを行うことでしょうか。

```
Test.hs:1:32: error:
    • Can't make a derived instance of ‘Num T’:
        ‘Num’ is not a stock derivable class (Eq, Show, etc.)
        Try enabling DeriveAnyClass ⬅️⬅️⬅️🤔🤔🤔
    • In the data declaration for ‘T’
  |
1 | data T = T deriving (Eq, Show, Num)
  |                                ^^^
```

GHC のエラーメッセージに従って LANGUAGE プラグマを追加する人は多そうなので、**よくわからないまま DeriveAnyClass を有効にしてコンパイルを通したらいつの間にか `undefined` を含む実行コードが生成された！**ということは十分起こりえそうです。

もちろん警告は（`-Wall` なしでも）出るので、ちゃんと警告に目を通すまともなプログラマーならそんなヘマはしないと思いますが……。

ちなみに、 DeriveAnyClass は associated type family (および、associated data family) を持つクラスに対しても使えます。Derive<strong>Any</strong>Class という名前は伊達ではないですね！[^derive-any-class-failing]（まともに使えるわけではry）

[^derive-any-class-failing]: もちろん、 DeriveAnyClass が使えないクラスも書けます。考えてみてください。

DeriveAnyClass の他の問題点としては、 GeneralizedNewtypeDeriving とバッティングする点があります。

```haskell
{-# LANGUAGE DeriveAnyClass, GeneralizedNewtypeDeriving #-}

newtype U = U Int deriving (Show, Num) -- Num はどうやって導出される？

main = print (U 0 - U 0)
```

バッティングした場合には DeriveAnyClass が優先されるので、上記のコードは「使い物にならない」 `Num` インスタンスを生成します。この問題に関しては後述の DerivingStrategies も参照してください。

（こういう DeriveAnyClass と GeneralizedNewtypeDeriving がバッティングした場合、専用の警告メッセージが出ます。

```
DerivingTest.hs:3:35: warning:
    • Both DeriveAnyClass and GeneralizedNewtypeDeriving are enabled
      Defaulting to the DeriveAnyClass strategy for instantiating Num
    • In the newtype declaration for ‘U’
  |
3 | newtype U = U Int deriving (Show, Num) -- Num はどうやって導出される？
  |                                   ^^^
```

GHC 8.10ではこの警告に `-Wderiving-defaults` という名前がつき、ON/OFFを切り替えられるようになりました（[GHC User's Guide](https://downloads.haskell.org/~ghc/8.10.1/docs/html/users_guide/using-warnings.html#ghc-flag--Wderiving-defaults)）。この警告自体は以前から存在して、 `-Wall` なしでも有効なので、古いGHCを使っている方も安心してください。）

DeriveAnyClass はこのような問題を抱えているので、個人的な意見としては「`instance NFData Foo` みたいな、**そもそも1行で済む定義を省略するためにわざわざ使うほどではない**」と思います。プロジェクトのコーディング規約によって禁止するのもアリかもしれません。

# GeneralizedNewtypeDeriving

イギリス英語 vs アメリカ英語 のアレがあるので綴りが2種類あります（-ised の方は GHC 8.6 以降）。よくGNDと省略されます。

newtype元のインスタンスをそっくりそのままnewtype先でも使い回したいことが時々あります。

```haskell:GNDを使わない例
newtype T = T Int deriving (Show)

-- Num Int と同等にしたい
instance Num T where
  negate (T x) = T (negate x)
  T x + T y = T (x + y)
  T x * T y = T (x * y)
  fromInteger n = T (fromInteger n)

main = print (T 0 - T 0)
```

これを可能にするのがGNDです。

```haskell:GNDを使う例
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype T = T Int deriving (Show, Num)

main = print (T 0 - T 0)
```

大昔のGND[^ghc-version]には、GADTsだかtype familiesだかと組み合わせることで型安全性を破壊する問題がありましたが、 GHC 7.8 で type role と safe coercion が導入されたことにより、GNDも安全になりました。type role と safe coercion はそれだけで記事が数本書けるネタなのでここでは割愛します。

[^ghc-version]: 現在のGHCのマニュアルではGNDやGADTsが「Since 6.8.1」となっていますが、GNDとGADTsは機能としてはそれ以前から存在しました。GHC 6.8.1はあくまで、各種GHC拡張を `-X` オプションや `LANGUAGE` プラグマで指定できるようになったバージョンです（それ以前はGHC拡張は `-fglasgow-exts` やその他の `-f` オプションで制御されていました。一部の拡張は `LANGUAGE` で制御できたのかな？）。ちなみに TypeFamiliesは本当に GHC 6.8 頃から実装が始まったようです。

## GNDの制限

GNDが安全になった代償として、ある種の型クラスに対してGNDが使用できない状況が発生するようになりました。具体例の一つは `join :: m (m a) -> m a` みたいなメソッドを含むクラスです[^join]。

[^join]: 一律ダメというわけではなくて、 `newtype M a = M (Maybe a) deriving Join` のような、newtypeの右辺に既知の型が来る場合は大丈夫のようです。

```haskell
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

class Join m where
  join :: m (m a) -> m a

newtype MyMonadT m a = MyMonadT (m a)
  deriving Join
```

```
TestGND.hs:7:12: error:
    • Couldn't match representation of type ‘m (MyMonadT m a)’
                               with that of ‘m (m a)’
        arising from the coercion of the method ‘join’
          from type ‘forall a. m (m a) -> m a’
            to type ‘forall a. MyMonadT m (MyMonadT m a) -> MyMonadT m a’
      NB: We cannot know what roles the parameters to ‘m’ have;
        we must assume that the role is nominal
    • When deriving the instance for (Join (MyMonadT m))
  |
7 |   deriving Join
  |            ^^^^
```

この記事の主題とは外れるかもしれませんが、この問題はQuantifiedConstraintsを使って型のtype roleがrepresentationalであることを教えてやれば回避できます。詳しくは Ryan Scott 氏の [How QuantifiedConstraints can let us put join back in Monad](https://ryanglscott.github.io/2018/03/04/how-quantifiedconstraints-can-let-us-put-join-back-in-monad/) を読んでください。動作するコードだけ提示すると、次のようになります[^standalone-deriving-for-join]：

[^standalone-deriving-for-join]: 標準のderiving記法では `Join (MyMonadT m)` の定義の際に追加の制約を記述できないので、StandaloneDerivingを使っています。

```haskell
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE QuantifiedConstraints #-}
import Data.Coerce

class Join m where
  join :: m (m a) -> m a

newtype MyMonadT m a = MyMonadT (m a)

deriving instance (Join m, forall a b. Coercible a b => Coercible (m a) (m b)) => Join (MyMonadT m)
```

join以外の例としては

```haskell
class Foo a where
  foo :: Applicative f => f a
```

があります。こっちはQuantifiedConstraintsを使って

```haskell
class Foo a where
  foo :: (Applicative f, forall x y. Coercible x y => Coercible (f x) (f y)) => f a
```

と書いてもダメそうです（GHC 8.8.1およびGHC 8.10.1で確認）。なぜだ〜〜

GNDの制限事項としては他に、associated type familyやassociated data familyへの対応があります。associated data familyを持つ型クラスにはGNDは使えません。associated type familyにはGHC 8.2で対応しましたが、UndecidableInstances拡張が必要となります。

UndecidableInstancesというのは型検査機が停止しない可能性のあるコードをコンパイルする際に必要な拡張です。GNDにより実際に型検査機が停止しなくなる例[^undecidable-instances-note]を挙げておきます：

[^undecidable-instances-note]: こういう病的な例のために一律でUndecidableInstancesが必要になるのはどうかと思うので、もうちょっとなんとかならないのかと思いますが、どうなんですかね？

```haskell
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE UndecidableInstances #-}

class Foo a where
  type Foo' a

newtype X = X X deriving Foo

x :: Foo' X -- この型はなに？？？
x = undefined
-- ちなみに x = x だとコンパイルが通る（偶然かもしれない）

main = return ()
```

# DerivingVia

*Since: 8.6.1*

DerivingViaは、GHC 8.6で実装されたすごいやつです。ググれば色々解説が出てくると思うので、ここでは割愛します。

ここではmr_konn氏の投稿・スライドを紹介しておきます：

- [Type-driven safe derivation of ToJSON and FromJSON, using DerivingVia in GHC 8.6 and some type-level hacks](https://www.reddit.com/r/haskell/comments/8y98yt/typedriven_safe_derivation_of_tojson_and_fromjson/) 2018年7月
- [本当はすごい newtype](https://speakerdeck.com/konn/ben-dang-hasugoi-newtype) ([コード](https://github.com/konn/newtype-talk-five)) 2018年8月
    - 不健全だった頃のGNDやroles周りにも触れている

DerivingViaは単に便利なシンタックスシュガーというだけではなく、これまでできなかった抽象化を行えるようになります。詳しくは私の記事

* [unboxing-vectorの紹介：newtypeフレンドリーなunboxed vector](https://qiita.com/mod_poppo/items/cf6b66ff16464c170ac2)
* [抽象型クラスで型クラスの変更の非互換性を緩和する](https://qiita.com/mod_poppo/items/b168b208ea3776676b1f)

を読んでください。

なお、DerivingViaはGNDの一般化であり、「GNDの制限」に書いた内容がDerivingViaにもそのまま当てはまります。

# DerivingStrategies

*Since: GHC 8.2.1*

DerivingStrategiesを使うと、deriving戦略 (stock, newtype, anyclass, via[^strategy-via]) をソースコード上に明示できます。例えばこんな感じです：

[^strategy-via]: DerivingViaはそもそもDerivingStrategiesを前提とした文法なので、話の順番が逆になっています。DerivingStrategiesによって何かが変わるというわけではありません。

```haskell
newtype T = T Int
  deriving stock (Eq, Generic)
  deriving newtype Show
  deriving anyclass NFData
```

これができるとどういう状況で嬉しいかというと、

1. 可読性が上がる
2. 通常はstock instanceが生成される状況においてGNDを使用することができる
3. GNDとDeriveAnyClassがバッティングする状況でGNDを選択することができる

の3点でしょうか。

2番目の「通常はstock instanceが生成される状況においてGNDを使用」というのは、

```haskell
newtype T = T Int deriving newtype Show
```

によって `show (T 123) == "123"` となるような `Show T` のインスタンスが得られる、という意味です。

ちなみに、stock derivingできるクラスについてstockとGNDを比較すると、

* stockとGNDで挙動が変わらない：`Eq`, `Ord`, `Ix`, `Bounded`, `Functor`, `Foldable`
* stockとGNDで挙動が変わる：`Read`, `Show`, `Generic`
* newtypeに対するstock derivingに対応していない：`Enum`
* GNDに対応していない：`Data`, `Typeable`
    * `Data` はtype roleの関係でダメ。 `Typeable` はstockしか使えない[^gnd-typeable]。

[^gnd-typeable]: `deriving newtype Typeable` と書いても完全に無視されます。GHCがもう少し親切なら警告を出してくれたかもしれませんが、まあこんなコードをわざわざ書く人はいないし問題にはならないでしょう。

となります。つまり、DerivingStrategiesによって新たに `Read`, `Show`, `Generic` に対してGNDできるようになります。

3番目のGNDとDeriveAnyClassがバッティングする状況でGNDを選択、というのはDeriveAnyClassのところですでに説明しました。まあこの記事を読んであなたがDeriveAnyClassを避けるようになればそもそもバッティングが起こらないので、3番目のメリットは事実上存在しないかもしれません。

さて、「これからはDerivingStrategiesの時代だ！deriving戦略をガンガン書いていきたい！むしろ戦略を必須にしたい！」と思ったあなたに朗報です。GHC 8.8では[警告オプション `-Wmissing-deriving-strategies`](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-warnings.html#ghc-flag--Wmissing-deriving-strategies) が導入されました。戦略が指定されていないderiving節に警告が出ます。

# StandaloneDeriving

deriving節は通常はデータ型の定義箇所に書きますが、型の定義とは独立に書けると便利なことがあります。StandaloneDerivingを使うとそれが可能になります。

書き方は、例えば次のようになります：

```haskell
deriving instance Show Foo
```

見ての通り、行頭に「deriving」がつきます。あとは通常のインスタンス宣言と同様です（ただし where 節は続かない）。

StandaloneDerivingが役に立つのはどんな状況でしょうか。

まず一つは、新たな型クラスを定義する際に既存のnewtypeに対するGNDやDerivingViaを使いたい時です。

例えば、 `Num` クラスみたいな数値クラスを再発明するとしましょう（詳しくは私の記事「[Haskell でオレオレ Num クラスを作るための考察](https://blog.miz-ar.info/2017/05/redesigning-numeric-classes-for-haskell/)」を参照）。`Int` や `Double` 等の基本的な数値型はもちろんインスタンスにします。

```haskell
class Additive a where
  zero :: a
  add :: a -> a -> a

instance Additive Integer where ...
instance Additive Int where ...
instance Additive IntNN where ... -- NN = 8, 16, 32, 64
instance Additive Word where ...
instance Additive WordNN where ... -- NN = 8, 16, 32, 64
instance Additive Float where ...
instance Additive Double where ...
```

このほか、 `Foreign.C.Types` で定義されるFFI用の数値型（`CInt` とか `CLong` とか）もそのクラスのインスタンスにしておきたいところです。ですが、FFI用の数値型はやたら種類が多くて大変です。

実はFFI用の数値型は `IntNN`, `WordNN`, `Float`, `Double` などのnewtypeなので、GNDが使えればすでに定義した `IntNN` 等のインスタンスから自動でインスタンスを導出できます。型の定義箇所とは違う場所でGNDしたい……そう、StandaloneDerivingの出番です。

```haskell
...
deriving instance Additive CInt
deriving instance Additive CUInt
deriving instance Additive CLong
...
```

StandaloneDerivingの別のメリットとして、手動で制約を書けるという点があります。通常のderivingでは必要な制約をGHCが自動で推論しますが、StandaloneDerivingではプログラマーが手動で制約を書けます。こちらの具体例は、「GNDの制限」のところですでに紹介しました。

あとはGADTsに対して普通のderivingは使えないけどStandaloneDerivingは使える、みたいな話もあるようです。詳しくは[User's Guide](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#stand-alone-deriving-declarations)を読んでください。

ちなみに、DerivingStrategiesとStandaloneDerivingを併用する際は、戦略を「deriving」と「instance」の間に書きます。DerivingViaも同様です[^standalonederiving-via]。

[^standalonederiving-via]: GHC 8.8時点のマニュアルにはこの用法についての記載がありませんが、[GHC 8.10のマニュアル](https://downloads.haskell.org/~ghc/8.10.1/docs/html/users_guide/glasgow_exts.html#deriving-via) では直りました。

```haskell
newtype Foo = Foo Int

-- With DerivingStrategies:
deriving stock instance Generic Foo
deriving newtype instance Num Foo
deriving anyclass instance NFData Foo
deriving via Sum Int instance Semigroup Foo
```

ところで `deriving anyclass instance NFData Foo` ってただの文字数の無駄遣いですね。普通に `instance NFData Foo` って書けば良いので。

# 最後に

derivingはうまく使えば手書きするコード量を削減できます。derivingを使いこなして、快適なHaskellライフを送りましょう！