「導出」機構の拡張
Haskell 98はプログラマは指定された型クラスの標準インスタンスを生成するために,データ型宣言でderiving節を追加することを許している.GHCはこの機構を複数の軸に沿って拡張する:
standaloneな導出機構を用いることで,導出の機構をデータ型宣言と独立して使うことができる.
Haskell 98においては,導出可能なクラスは Eq・ Ord・ Enum・ Ix・ Bounded・ Read・Show のみである.様々な言語拡張により,この一覧を拡充することができる.
すべてのメソッド定義を生成することでインスタンスを導出するという標準的なアプローチ(訳注:stock戦略)の他に,GHCは任意のクラスを導出することのできる2つの追加の導出戦略をサポートする.
newtypeの為の一般化されたnewtype戦略(GeneralisedNewtypeDeriving)
空のインスタンス宣言を用いたany class戦略
(訳注:今はDeriving Viaを用いたvia戦略も存在する)
特に,コンパイラがデフォルトで間違った戦略を選択した場合は,ユーザは必要に応じて導出戦略を宣言することができる.
Deriving instances for empty data types
-XEmptyDataDeriving
8.4.1以降
空のデータ型に対する標準型クラスのインスタンス導出を許す.
コンストラクタを持たないデータ型は-XEmptyDataDeclsフラグを用いて書くことができる (データ型と型シノニムの拡張(和訳) を参照せよ).このフラグはHaskell 2010においてはデフォルトで有効である.デフォルトで有効になっていないのは,これらの型に対して型クラスを導出する能力である.この能力はフラグ -XEmptyDataDeriving を用いることによって有効化される.例えば,このフラグにより,以下のように書けるようになる. code: de1.hs
data Empty deriving (Eq, Ord, Read, Show)
これは,次のようなインスタンスを生成する.
code:de2.hs
instance Eq Empty where
_ == _ = True
instance Ord Empty where
compare _ _ = EQ
instance Read Empty where
readPrec = pfail
instance Show Empty where
showsPrec _ x = case x of {}
-XEmptyDataDerivingフラグは,これら4つの「標準的な」(Haskell Reportで言及されている)型クラスの導出を有効化する際だけに要求される.以下でもっと詳しく説明される,他の導出機構の拡張は,-XEmptyDataDerivingを空のデータ型と共に用いることを要求しない.そのような拡張には,以下のようなものがある.
-XStandaloneDeriving (独立した導出宣言を参照せよ)
-XDeriveAnyClass (他の任意のクラスの導出を参照せよ)
deriving節のコンテキストの推論
Haskellレポートではderiving節がいつ合法であるかについて曖昧である.例えば:
code: de3.hs
data T0 f a = MkT0 a deriving( Eq )
data T1 f a = MkT1 (f a) deriving( Eq )
data T2 f a = MkT2 (f (f a)) deriving( Eq )
自然に生成されたEqのコードは以下のインスタンス宣言を作るだろう:
code:de4.hs
instance Eq a => Eq (T0 f a) where ...
instance Eq (f a) => Eq (T1 f a) where ...
instance Eq (f (f a)) => Eq (T2 f a) where ...
最初の例は自明に大丈夫である,2つ目の例も大丈夫であるが,最初の例に比べて自明度は低い.3つ目の例はHaskell 98では不可であり,インスタンスの決定性を損なうリスクがある.
GHCは,前2つを受け入れ3つ目を拒絶するという,保守的な立ち位置をとる.これは,推論されたインスタンスのコンテキスト内の各制約は,型変数のみで構成されいる必要があり,(訳注:型変数の)繰り返しがあってはならないというルールになる.
このルールはフラグに関係なく適用される.もっと風変わりなコンテキストが欲しい場合は,standaloneな導出機構を用いて自分で書くことができる.
独立した導出宣言
StandaloneDeriving
6.8.1以降
独立した導出宣言の使用を許す.
GHCは独立したな deriving 宣言を許し,これは StandaloneDerivingフラグによって有効化される.
code:de5.hs
data Foo a = Bar a | Baz String
deriving instance Eq a => Eq (Foo a)
キーワード deriving(訳注:おそらくinstanceの間違い)の存在と where の部分の不存在という二点を除けば,構文は通常のインスタンス宣言と全く同じである.
しかしながら,standaloneな導出は以下のようないくつもの重要な点でderiving 節と異なる.
standaloneな導出宣言はデータ型の宣言と同じモジュールに在る必要がない.(ただし,orphanインスタンスの危険性を認識すること.(Orphanモジュールとインスタン宣言 を見よ))
殆どの場合において,通常のインスタンス宣言でするのと全く同様に,明示的なコンテキストを与える必要がある.(上記の例においては,コンテキストは (Eq a)である) (対照的に,データ型の宣言にくっついている deriving節では,コンテキストは推論される.)
この規則の例外は,単一の追加制約の追加ワイルドカードの制約がコンテキストとして使用されている場合に,standaloneの導出宣言のコンテキストがそのコンテキストを推論できるというものである.例えば:
code:de6.hs
deriving instance _ => Eq (Foo a)
これはdata Foo aの宣言の後に,deriving Fooを書いたのと基本的に同じである.この機能を使用するにはPartialTypeSignaturesを使用する必要がある(部分型シグネチャ). data宣言に付随するderiving宣言とは異なり,インスタンスは(FlexibleInstances も使用すると仮定すれば)データ型よりも具体的にすることができる(型クラスとインスタンスの宣言(和訳) 内の「インスタンス宣言先頭の制約の緩和」を参照せよ).次の例を考えてみよう. code:de7.hs
data Foo a = Bar a | Baz String
deriving instance Eq a => Eq (Foo a) deriving instance Eq a => Eq (Foo (Maybe a))
これは(Foo [a]) と (Foo (Maybe a)) のための導出されたインスタンスを生成するが,(Foo (Int,Bool)) といった他の型はEqのインスタンスにならない.
data宣言に付随するderiving 宣言と異なり,データ型の形式を制限しない.代わりに,GHCは単に,指定されたクラスのためのボイラープレートコードを生成し,それを型検査する.型エラーがあれば,それはユーザー側の問題である. (型エラーがある場合,GHCは問題のコードを表示する.)
これの利点は,ボイラープレートコードが実際に型検査に通るならば,GADTや他の風変わりなデータ型のインスタンスを導出できるということである.例えば,
code:de8.hs
data T a where
T1 :: T Int
T2 :: T Bool
deriving instance Show (T a)
この例では,TはGADTなので,Tのデータ型宣言に... deriving (Show)とすることはできないが,standalone導出を用いてインスタン宣言を生成することはできる.
欠点は,(訳注: 自動生成された)ボイラープレートコードが型検査に通らない場合,そのユーザが書いていないコードに関するエラーメッセージが表示されるという点である.一方で,deriving節を使うと,付帯条件は必然的により保守的になるが,エラーメッセージはよりわかりやすくなるだろう.
ほとんどの場合,全てのコンストラクタがスコープに入っているわけではないデータ型のインスタンスを作るのにstandaloneな導出を使うことはできない.これはなぜかというと,(訳注: もしこれを可能にしてしまうと)導出されたインスタンスは舞台裏にある(訳注: スコープに無い)コンストラクタを使用するコードを生成してしまうが,これでは抽象化が壊れてしまうからである.
このルールの唯一の例外は DeriveAnyClassである.というのも, DeriveAnyClass を用いてインスタンスを導出することというのは単に空のインスタンスを生成することであり,それには一切コンストラクタを使用する必要がないからである.詳細は任意クラスの導出のセクションを参照せよ.
それ以外に関しては,standalone導出は通常の導出と同様の規則に従う:
deriving instance宣言は同じフラグによって制御される通常のインスタン宣言と同様の形式及び決定に関する規則に従う必要がある. 型クラスとインスタンスの宣言(和訳) の中の「インスタンス宣言」を参照せよ. stand-alone構文は,通常のderiving節が一般化されるのと全く同じ方法で,newtypeを一般化する(newtypeの一般化されたインスタンス導出を参照せよ).例えば:
code:de9.hs
newtype Foo a = MkFoo (State Int a)
deriving instance MonadState Int Foo
GHCは常にインスタンスの最後のパラメータ(上の例ではFoo)をインスタンスが導出している型として扱う.
追加の型クラスのインスタンス導出 (Data, etc.)
newtypeの一般化されたインスタンス導出
GeneralisedNewtypeDeriving
GeneralizedNewtypeDeriving
6.8.1以降.イギリス綴り(GeneralisedNewtypeDeriving)は8.6.1以降.
GHCの巧妙なnewtypeの一般化された導出機構を有効にする.
newtypeを使って抽象的な型を定義するとき,型の内部表現が持つインスタンスをいくつか新たな型に継承させたいことがあるかもしれない.Haskell 98においては, Eq・ Ord・ Enum ・Boundedのインスタンスを導出することでこれらのインスタンスを継承することができるが,それ以外のクラスに関しては 明示的なインスタンス宣言を書かねばならない.例えば,以下の定義をして,
code:de10.hs
newtype Dollars = Dollars Int
Dollarsに対して演算をしたいときには,Numインスタンスを明示的に定義しなければならない.
code:de11.hs
instance Num Dollars where
Dollars a + Dollars b = Dollars (a+b)
...
インスタンスがやっているのは,newtype コンストラクタを適用したり取り除いたりすることだけである. コンストラクタは実行時に登場しない以上,このインスタンス宣言がIntの辞書と全く等価な,ただ遅いだけの辞書を定義しているだけだというのは特に腹立たしい.
DerivingVia (Deriving viaを参照せよ) はこの着想の一般化である.
deriving節の一般化
GHCは,そのようなインスタンスを代わりにGeneralizedNewtypeDeriving拡張を使って導出することを許すようになった.よって以下のように書ける.
code:de12.hs
newtype Dollars = Dollars { getDollars :: Int } deriving (Eq,Show,Num)
そして,実装はIntと同じNum辞書をDollarsに使用する.言い換えるなら,GHCは以下のコードのような(訳注: しかし少し違う)コードを生成し,
code:de13.hs
instance Num Int => Num Dollars
次に,可能な限りNum Intのコンテキストを単純化しようと試みる.GHCはスコープ内にNum Intのインスタンスがあることを知っているので,Num Intの制約を取り除くことができ,結果としてGHCが実際に生成するコードは以下のようになる.
code:de14.hs
instance Num Dollars
このインスタンスはNum Intインスタンスと同じコードで実装されているが,型検査を通すために必要に応じてDollarsとgetDollarsが追加されている,と考えることができる.(実際には,GHCはコード生成に対して多少異なるアプローチを用いる.詳細については,「より正確な仕様」の節を参照せよ.)
コンストラクタクラスのインスタンスも同様に導出できる.例えば,以下のようなStateモナド変換子とFailureモナド変換子を実装したとしよう.
code:de15.hs
instance Monad m => Monad (State s m)
instance Monad m => Monad (Failure m)
Haskell 98では,パースするモナドを以下のように定義できる.
code:de16.hs
type Parser tok m a = State tok (Failure m) a これは,上記のインスタンス宣言のおかげで自動的にモナドである.拡張を用いれば,以下のコードを介して,
code:de17.hs
newtype Parser tok m a = Parser (State tok (Failure m) a) deriving Monad
Monadクラスのインスタンスを書く必要がなくパーサー型を抽象化することができる.
この場合,導出されたインスタンス宣言は以下のような形を持つ.
code:de18.hs
instance Monad (State tok (Failure m)) => Monad (Parser tok m) Monadはコンストラクタクラスであるので,インスタンスはnewtypeの部分適用 であって,左辺全体ではないことに注意せよ.(訳注:これは)インスタンス宣言のコンテキストを生成するために型宣言が「η変換」されていると考えることができる.
newtypeがクラスの最後の引数であるという条件のもと,複数の引数を持つクラスのインスタンスすら導出することが可能である.この場合,deriving節の中にクラスの「部分適用」が登場する.例えば,以下のクラスがあるとき,
code:de19.hs
class StateMonad s m | m -> s where ...
instance Monad m => StateMonad s (State s m) where ...
Parserに対するStateMonadのインスタンスを以下のようにして導出できる.
code:de20.hs
newtype Parser tok m a = Parser (State tok (Failure m) a) deriving (Monad, StateMonad tok) 導出されたインスタンスは,新しい型へクラスを完全適用することによって得られる.
code:de21.hs
instance StateMonad tok (State tok (Failure m)) => StateMonad tok (Parser tok m) この拡張の結果として,newtype宣言内のすべての導出されたインスタンスは一様に扱われる(そして,単に表現型の辞書を再利用することによって実装される).ただし,newtypeと内部表現とで挙動が真に異なるShow とReadを除く.
注意
GeneralizedNewtypeDerivingを通してインスタンスを導出するとき,追加の言語拡張を有効にすることがしばしば必要になる.例えば,UnboxedTuples構文を使った簡単なクラスとインスタンスを考えてみよう:
code:de22.hs
{-# LANGUAGE UnboxedTuples #-} module Lib where
class AClass a where
aMethod :: a -> (# Int, a #) instance AClass Int where
コンパイラによって生成された導出されたインスタンスはUnboxedタプル構文を使用するため,次のコードは “Illegal unboxed tuple” エラーで失敗する.
code:de23.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-} import Lib
newtype Int' = Int' Int
deriving (AClass)
しかしながら,UnboxedTuples拡張を有効にすると,モジュールをコンパイルすることができる.次に挙げる例を含む様々な拡張で,同様のエラーが発生する可能性がある:
UnboxedTuples
PolyKinds
MultiParamTypeClasses
FlexibleContexts
より正確な仕様
導出されたインスタンスは,(任意の型シノニムの展開の後,)これらの形式の宣言に対してのみ導出を行う.
code:de24.hs
newtype T v1..vn = MkT (t vk+1..vn) deriving (C t1..tj)
newtype instance T s1..sk vk+1..vn = MkT (t vk+1..vn) deriving (C t1..tj)
ここで,
v1..vnは型変数であり, t, s1..sk, t1..tjは型である.
(C t1..tj) はCクラスの部分適用である.ここで,Cのアリティはちょうどj + 1である. つまり,Cはちょうど一つの型引数を欠いている.
kはC t1..tj (T v1...vk)がwell-kindedであるように(あるいは,データインスタンスの場合,C t1..tj (T s1..sk)がwell-kindedであるように)選ばれる.
型t は任意の型である.
型変数vk+1...vn は型 t・ s1..sk・ t1..tjの中に登場しない.
C は Read・ Show・Typeable・ Dataのいずれでもない.これらのクラスは型やコンストラクタの「中を覗く」べきではない.それでもnewtypeのこれらのクラスを導出することができるが,それはこの新しい機構を通してではなく,通常の方法で行える.(cf. この文書の「デフォルトの導出戦略」セクション)
Cの各メソッドをcoerceするのは安全である.つまり,Cの不足している最後の引数は,どのCのメソッドでもnominal roleでは使われない(Rolesを参照せよ).
それらが,GND(訳注:GeneralizedNewtypeDerivingのこと)と関連型の節で提示された要件を満たすならば,Cが関連型族を持つことが許される.
結果として,導出されたインスタンス宣言は以下のような形式になる.
code:de25.hs
instance C t1..tj t => C t1..tj (T v1...vk)
Cにいかなる型クラスメソッドも含まれていない場合,インスタンスコンテキストは全く不要であり,GHCは変わりに以下を生成することに注意せよ:
code:de26.hs
instance C t1..tj (T v1..vk)
うまくいかない例としては,以下の例がある.
code:de27.hs
newtype NonMonad m s = NonMonad (State s m s) deriving Monad
この場合,以下のインスタンスを導出することはできない.
code:de28.hs
instance Monad (State s m) => Monad (NonMonad m)
なぜなら,型変数s は State s m内に登場し,したがって「η簡約」することができないからである.このderiving節が拒否されるのは良いことである.というのも, NonMonad mは実はモナドではないからである(同じ理由によって).>>=を試しに正しい型で定義することを試みてみるとよいだろう.正しい型で定義するのは不可能だ.
最後の型クラスパラメータのインスタンスしか導出できないため,型クラスパラメータの順番が重要になることにも注意すること.
もし仮に上記の StateMonadクラスが以下のように定義されていたなら,
code:de29.hs
class StateMonad m s | m -> s where ...
上記のParser型のインスタンスを導出することはできなかっただろう.我々は,パラメータを複数持つ型クラスは通常,新しいインスタンスの導出が最も有意義な一つの「メイン」のパラメータを持つと仮定する.(訳注:大抵の場合導出する意義が最もあるパラメータが一つあるので,ユーザはそれを最後に持ってくるよう注意してね,みたいな意図かと)
最後に,以上のこれらはRead・ Show・Typeable・Dataでないクラスにのみ適用される.Read・Show・Typeable・Dataに対してはstock戦略が適用される. (Haskellレポートの4.3.3節を参照せよ). (標準クラス,Eq・ Ord・ Ix・Boundedでは,stock戦略が使われているのか,ここで説明されている戦略が使われているのかは重要ではない.)
関連型族
GeneralizedNewtypeDerivingは,関連型族を持ついくつかの型クラスに対しても機能する.例えば:
code:de30.hs
class HasRing a where
type Ring a
newtype L1Norm a = L1Norm a
deriving HasRing
導出されたHasRingのインスタンスは次のようになる.
code:de31.hs
instance HasRing (L1Norm a) where
type Ring (L1Norm a) = Ring a
正確には,導出される型クラスが次の形式の場合で,
code:de32.hs
class C c_1 c_2 ... c_m where
type T1 t1_1 t1_2 ... t1_n
...
type Tk tk_1 tk_2 ... tk_p
newtypeが次の形式の場合,
code:de33.hs
newtype N n_1 n_2 ... n_q = MkN <rep-type>
結果として, N n_1 n_2 ... n_qのC c_1 c_2 ... c_(m-1) のインスタンスを導出する.ただし,以下の条件を満たしていなければならない:
型パラメータc_mはT1からTkの型変数のそれぞれに1回出現する.この条件が成り立たないクラスを考えてみよう.例えば:
code:de34.hs
class Bad a b where
type B a
instance Bad Int a where
type B Int = Char
newtype Foo a = Foo a
deriving (Bad Int)
Bad Intのインスタンスを導出する場合,GHCは次のようなものを生成する必要がある:
code:de35.hs
instance Bad Int (Foo a) where
type B Int = B ???
B族のインスタンスの右辺にあるaを参照する方法がないので,今これは動かず,このインスタンスではGeneralixedNewtypeDerivingの設定では全く意味がない.
Cはいかなる関連データ族も持たない(型族のみを持つ).データ族が禁止されている理由を確認するには,次のシナリオを考えてみよう.
code:de36.hs
class Ex a where
data D a
instance Ex Int where
data D Int = DInt Bool
newtype Age = MkAge Int deriving Ex
Exのインスタンスを導出する場合,GHCは次のようなものを生成する必要がある:
code:de37.hs
instance Ex Age where
data D Age = ???
しかし,各データ族インスタンスが新しいデータコンストラクタを生成しなければならないため,GHCが???に何を入れるかは明確ではない.
両方の条件が満たされた場合,GHCは以下のようなインスタンスを生成する:
code:de38.hs
instance C c_1 c_2 ... c_(m-1) <rep-type> =>
C c_1 c_2 ... c_(m-1) (N n_1 n_2 ... n_q) where
type T1 t1_1 t1_2 ... (N n_1 n_2 ... n_q) ... t1_n
= T1 t1_1 t1_2 ... <rep-type> ... t1_n
...
type Tk tk_1 tk_2 ... (N n_1 n_2 ... n_q) ... tk_p
= Tk tk_1 tk_2 ... <rep-type> ... tk_p
繰り返しになるが,Cに型クラスメソッドが含まれていない場合,インスタンスのコンテキストは冗長になるので,GHCは代わりにインスタンスC c_1 c_2 ... c_(m-1) (N n_1 n_2 ... n_q)を生成する.
この機能を用いるために UndecidableInstances拡張を有効化する必要がある場合があることに注意せよ.以下はなぜこれが起こるかもしれないかを説明する異常な例である.
code:de39.hs
class C a where
type T a
newtype Loop = MkLoop Loop
deriving C
これは,以下のような導出されたインスタンスを生成する:
code:de40.hs
instance C Loop where
type T Loop = T Loop
ここで,T Loop型を使おうとすると,その定義が際限なく再帰するので,型検査が無限ループすることは明らかである.他の場合には,生成されたコードが型検査をループに陥れなかったとしても,UndecidableInstancesを有効にする必要があるかもしれない.例えば:
code:de41.hs
instance C Int where
type C Int = Int
newtype MyInt = MyInt Int
deriving C
これは,次のような導出されたインスタンスを生成する:
code:de42.hs
instance C MyInt where
type T MyInt = T Int
T MyIntの型検査は停止性を持つが,GHCの停止性検査機はそのことを判定できるほど賢くはないので,この導出されたインスタンスを使うには UndecidableInstances を有効化する必要がある.この方法を行う場合は,導出するすべての型族インスタンスが使われるときに最終的に停止することの確信を持ってからにすること!
DerivingVia(Deriving viaを参照せよ)は,関連型族のインスタンスを導出するために本質的に同じ仕様を使うことに注意せよ.(ただし,基になるnewtypeのrep-typeの代わりにvia typeを使用する.)
他の任意のクラスの導出
DeriveAnyClass
7.10.1以降
任意の型クラスに対してderiving節を使用することを許す.
DeriveAnyClassを用いて,他のクラス(訳注:前述のクラス以外のものという意味)の導出できる.コンパイラは明示的に定義されたメソッドなしでインスタンス宣言を生成する.これは最小集合が空のクラス,特に総称関数を書くときに非常に役立つ.
例として,プリティな文字列を出力するシンプルなプリティプリンタクラス SPrettyを考えよう.
code:de43.hs
{-# LANGUAGE DefaultSignatures, DeriveAnyClass #-} class SPretty a where
sPpr :: a -> String
default sPpr :: Show a => a -> String
sPpr = show
ユーザがsPprに手動の実装を提供していないなら,デフォルトのshowになる.これで,DeriveAnyClass拡張を利用して,新しいデータ型のSPrettyインスタンスを簡単に実装することができる:
code:de44.hs
data Foo = Foo deriving (Show, SPretty)
上記のコードは以下のコードと等価である.
code:de45.hs
data Foo = Foo deriving Show
instance SPretty Foo
つまり,全てのメソッドに関して空の実装で SPretty Foo インスタンスが生成される.この例では DefaultSignatures を用いているため, sPpr のデフォルト実装は自動的に埋められる.(訳注:原文はfill inを用いているが,これはデフォルトインスタンスをよしなに定義するという行動を書類の記入と同じニュアンスとして解釈しこの言葉を使っているようである.この訳ではfill inを埋めると訳すことにしている.)
以下の詳細事項に注意すること.
newtypeに対して何らかのクラスを導出しようとしていて, GeneralizedNewtypeDeriving もオンである場合,DeriveAnyClassが優先される.
インスタンスのコンテキストは導出されたクラスのメソッドの型シグネチャによって決定される.例えば,クラスが
code:de46.hs
class Foo a where
bar :: a -> String
default bar :: Show a => a -> String
bar = show
baz :: a -> a -> Bool
default baz :: Ord a => a -> a -> Bool
baz x y = compare x y == EQ
であって, DeriveAnyClassを使ってこれを導出しようと試みた場合,
code:de47.hs
instance Eq a => Eq (Option a) where ...
instance Ord a => Ord (Option a) where ...
instance Show a => Show (Option a) where ...
data Option a = None | Some a deriving Foo
導出された Fooインスタンスは
code:de48.hs
instance (Show a, Ord a) => Foo (Option a)
となる.なぜなら, bar と baz のデフォルトの型シグネチャはそれぞれShow a 制約と Ord a 制約を要求するからである.
非デフォルトな型シグネチャもインスタンスの制約を推論する際に役割を果たしうる.例えば,以下のクラスがあるとき,
code:de49.hs
class HigherEq f where
(==#) :: f a -> f a -> Bool
default (==#) :: Eq (f a) => f a -> f a -> Bool
x ==# y = (x == y)
このクラスのインスタンスを導出しようと試みると,
code:de50.hs
instance Eq a => Eq (Option a) where ...
data Option a = None | Some a deriving HigherEq
結果として,これはエラーで失敗する:
code:de
No instance for (Eq a)
arising from the 'deriving' clause of a data type declaration
これはなぜかというと, (==#)のデフォルト型シグネチャからEq (Option a) が必要とされ,それによってEq aが必要とされるのに,スコープにはEq aがないからである.しかし, HigherEq の定義を少しいじると,
code:de51.hs
class HigherEq f where
(==#) :: Eq a => f a -> f a -> Bool
default (==#) :: Eq (f a) => f a -> f a -> Bool
x ==# y = (x == y)
「こうすれば(==#)に対するデフォルト以外の型シグネチャがEq a制約をもたらす」というのだけが唯一の違いであることに注意せよ.デフォルト以外の型シグネチャからの制約は,導出されたインスタンスのコンテキスト自体には決して表れないが,デフォルトの型シグネチャによって要求される責務から解放されるのに使用できる.上の例では,デフォルトの型シグネチャがEq aのインスタンスを要求し,デフォルト以外の型シグネチャがその要求を満たすことができたので,導出されたインスタンスは単純に次のようになる:
code:de52.hs
instance HigherEq Option
DeriveAnyClassは部分適用されたクラスに用いることができる.例えば,以下のコード
code:de53.hs
data T a = MKT a deriving( D Int )
は,以下を生成する.
code:de54.hs
instance D Int a => D Int (T a) where {}
DeriveAnyClassは,関連型族のデフォルトインスタンスを埋める(訳注:これもfill in)のに使用できる:
code:de55.hs
{-# LANGUAGE DeriveAnyClass, TypeFamilies #-} class Sizable a where
type Size a
type Size a = Int
data Bar = Bar deriving Sizable
doubleBarSize :: Size Bar -> Size Bar
doubleBarSize s = 2*s
deriving( Sizable )は以下と同様である.
code:de56.hs
instance Sizeable Bar where {}
結果として,デフォルトから関連型(訳注:関連型の実装)を埋める(訳注:これもfill in)ための通常の規則(訳注:ここではtype Size a = Intのこと)が適用され,Size BarはIntと等しくなる.
導出戦略
DerivingStrategies
8.2.1以降
それぞれがオプションで戦略で修飾された,複数のderivingを許す.
ほとんどの場合で,すべてのderiving宣言は曖昧性なく型クラスのインスタンスを生成する.しかし,GeneralizedNewtypeDerivingとDeriveAnyClass拡張の両方を同時に有効にすると,導出が曖昧になるかもしれないというコーナーケースがある.以下の例を考えてみよう:
code:de57.hs
{-# LANGUAGE DeriveAnyClass, GeneralizedNewtypeDeriving #-} newtype Foo = MkFoo Bar deriving C
Cを導出するためのDeriveAnyClassアプローチか,Cを導出するためのGeneralizedNewtypeDerivingアプローチのどちらかを選ぶことができ,どちらも同じくらい有効である.GHCはデフォルトではそのような対立ではDeriveAnyClassを支持するが,これは満足のいく解決策ではない.なぜなら,それはユーザが単一のモジュールで両方の言語拡張を使用できないままにするからである.
これらをよりロバストにするために,GHCは導出戦略という概念を持っている.これは,インスタンスを導出する際にどのアプローチを使用するかを明示的に要求することを可能にする.この機能を有効にするには,DerivingStrategies拡張を有効にする必要がある.導出戦略はderiving 節内か,
code:de58.hs
newtype Foo = MkFoo Bar
deriving newtype C
またはstandaloneなderiving宣言内で指定できる.
code:de59.hs
deriving anyclass instance C Foo
DerivingStrategiesは一つのデータ宣言に対して複数のderiving節を用いることをも可能にする.これにより,あるインスタンスについてはある導出戦略を用い,他のインスタンスについては別の導出戦略を用いる,といったことが可能になる.例えば,
code:de60.hs
newtype Baz = Baz Quux
deriving (Eq, Ord)
deriving stock (Read, Show)
deriving newtype (Num, Floating)
deriving anyclass C
現在存在する導出戦略は以下の通りである.
stock: 可能であれば,GHCにデータ型の「標準的な」 インスタンスを実装させる (例: Eq・Ord・Generic・Data・Functorなど)
anyclass: DeriveAnyClassを用いる (他の任意のクラスの導出を見よ)
newtype: GeneralizedNewtypeDerivingを用いる (newtypeの一般化されたインスタンスの導出を見よ)
via: DerivingViaを用いる (Deriving viaを見よ)
デフォルトの導出戦略
明示的に導出戦略が与えられていない場合,複数の戦略が適用できる可能性がある.そのような場合,GHC は以下のようにして戦略を選ぶ.
1. stock型クラス,つまりHaskellレポートで指定されているクラスと言語拡張によって有効になっているクラスは,次の例外を除いて,stock戦略を使用して導出される:
newtypeでは,GeneralizedNewtypeDerivingが有効になっていなくても,Eq, Ord, Ix, Boundedは常にnewtype戦略を使って導出される.(stock戦略を使用して導出された事例と観察可能な違いはないはずである.)
newtypeについても,GeneralizedNewtypeDerivingが有効になっていて導出が成功した場合,Functor, Foldable, Enumはnewtype戦略を使って導出される.
2. それ以外の型クラスに関しては,
1. DeriveAnyClass が有効化されているときはanyclassを用いよ.
2. GeneralizedNewtypeDerivingが有効化されていてnewtypeの導出をしている場合,newtypeを用いよ.
両方のルールが単一のderiving節に適用できる場合, anyclassが使われ,ユーザはその曖昧性について警告を受ける.警告を避けるには,望みの導出戦略を明示的に述べれば良い.
Deriving via
DerivingVia
DerivingStrategiesを含む
8.6.1以降
すでにそのクラスのインスタンスである(2つの間にCoercibleのインスタンスが存在するような: Coercible制約を参照せよ)同等の実行時表現を持つ別の型を指定することで,そのクラスのインスタンスを導出することを可能にする.
DerivingViaはvia導出戦略によって示される.viaはcoerceを実行するために別の型(via型)を指定する必要がある.例えば,次のコードは
code:de61.hs
{-# LANGUAGE DerivingVia #-} import Numeric
newtype Hex a = Hex a
instance (Integral a, Show a) => Show (Hex a) where
show (Hex a) = "0x" ++ showHex a ""
newtype Unicode = U Int
deriving Show
via (Hex Int)
-- >>> euroSign
-- 0x20ac
euroSign :: Unicode
euroSign = U 0x20ac
以下のインスタンスを生成する.
code:de62.hs
instance Show Unicode where
show :: Unicode -> String
show = Data.Coerce.coerce
@(Hex Int -> String)
@(Unicode -> String)
show
この拡張は GeneralizedNewtypeDerivingを一般化する. GNDでNum Unicodeを導出する(deriving newtype Num)ためには,Num Intのインスタンスを再利用しなければならない.Deriving Viaを使えば,表現型Intを明示的に指定することができる.
code:de63.hs
newtype Unicode = U Int
deriving Num
via Int
deriving Show
via (Hex Int)
euroSign :: Unicode
euroSign = 0x20ac
インスタンスの宣言において,コードの重複というのはよくあることである.
慣れ親しんだパターンとして,Applicativeファンクタの上に演算を持ち上げるというのがある.そのような他のすべてのインスタンスと重なる以下のようなf aの包括的なインスタンス
code:de64.hs
instance (Applicative f, Semigroup a) => Semigroup (f a) ..
instance (Applicative f, Monoid a) => Monoid (f a) ..
を持つ代わりに,newtype App(App f aとf aがメモリ内で同一の表現を持つ)を作り,DerivingViaを用いてこのパターンの使用を明示的に有効にすることができる:
code:de65.hs
{-# LANGUAGE DerivingVia, DeriveFunctor, GeneralizedNewtypeDeriving #-} import Control.Applicative
newtype App f a = App (f a) deriving newtype (Functor, Applicative)
instance (Applicative f, Semigroup a) => Semigroup (App f a) where
(<>) = liftA2 (<>)
instance (Applicative f, Monoid a) => Monoid (App f a) where
mempty = pure mempty
data Pair a = MkPair a a
deriving stock
Functor
deriving (Semigroup, Monoid)
via (App Pair a)
instance Applicative Pair where
pure a = MkPair a a
MkPair f g <*> MkPair a b = MkPair (f a) (g b)
via 型は newtypeでなくともよいことに注意せよ.唯一の制限は,元のデータ型とcoercibleでなければいけないということだけである.これはつまり,次の例のように,任意の個数のnewtypeのネストがあってもよいということである.
code:de66.hs
newtype Kleisli m a b = (a -> m b)
deriving (Semigroup, Monoid)
via (a -> App m b)
ここでは, Monoid ((->) a) インスタンスを利用している.