Haskell Reportは、正確にどんな場合にderiving
節が有効かについてはっきりしない。例を示す。
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
のコードは以下のようなインスタンス宣言になるだろう。
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 ...
最初のものは明らかに問題ない。二番目も、それほど明らかではないが、問題ない。三番目はHaskell 98でなく、インスタンスの停止性を損なう危険がある。
GHCは保守的な立場を採っている。最初の二つは受け付けるが、三番目は受け付けない。規則はこうである。推論された文脈中の各制約は、型変数のみから成っていなければならず、型変数に重複があってもならない。
この規則はフラグに関係なく適用される。もっと風変りな文脈が必要なら、独立derivingの機構を使って自分で書くことができる。
GHCは単独のderiving
宣言を受け付けるようになった。これは-XStandaloneDeriving
で有効になる。
data Foo a = Bar a | Baz String deriving instance Eq a => Eq (Foo a)
構文は通常のインスタンス宣言と同じであるが、(a)deriving
キーワードを使うことと、(b)where
部がないことが異なる。以下の点に注意せよ。
通常のインスタンス宣言でするのと全く同じように、文脈(この例では文脈は(Eq a)
)を明示的に指定しなければならない。(対照的に、データ型宣言に付属したderiving節においては文脈は推論される)。
deriving instance
の宣言は、形式と停止性に関して、通常のインスタンス宣言が従うのと同じ規則に従い、同じフラグで制御される。7.6.3. インスタンス宣言を見よ。
data
宣言に付属するderiving
宣言と異なり、インスタンスはデータ型よりも限定的になり得る(-XFlexibleInstances
を使うことを前提にすれば。7.6.3.2. インスタンス文脈に関する規則の緩和)。例として以下を考えよ。
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は指定されたクラスのための定型コードを生成し、それを型検査する。もし型エラーがあれば、それはあなたの問題である。(型エラーがあるなら、違反のあるコードをGHCが表示するだろう)。この方式の利点は、定型コードさえ型検査に通れば、GADTその他の妙なデータ型に対してインスタンスを自動導出できることである。例を挙げる。
data T a where T1 :: T Int T2 :: T Bool deriving instance Show (T a)
この例では、T
のデータ型宣言に付属して... deriving( Show )
とは言えない。T
はGADTだからである。しかし、独立derivingを使えば、このインスタンスを導出することができる。
この独立deriving構文は、通常のderiving
がnewtypeに対して一般化された(7.5.4. newtypeについての自動導出インスタンスの一般化)のと全く同じように一般化される。例えば次のように。
newtype Foo a = MkFoo (State Int a) deriving instance MonadState Int Foo
GHCは常に、インスタンスの最後
のパラメタ(この例ではFoo
)を導出対象の型として扱う。
Typeable
、Data
など)
Haskell 98では、プログラマがデータ型宣言に「deriving( Eq, Ord )
」と付け加えれば、これらのクラスの標準的なインスタンス宣言が生成される。Haskell 98では、deriving
節に現れることのできるクラスは標準のEq
、Ord
、Enum
、Ix
、Bounded
、Read
、Show
だけである。
GHCはこれを拡張し、新たにいくつかのクラスが自動的に導出できるようにする。
-XDeriveDataTypeable
を使うと、Typeable
とData
の両クラスのインスタンスを自動導出できる。これらのクラスは、ライブラリ中、それぞれData.Typeable
とData.Generics
で定義されている。
Typeable
のインスタンスは、データ型の型パラメタの数が七個以下で、その全てが種*
を持っているときのみ自動導出できる。これは、Typeable
クラスがScrap More Boilerplate: Reflection, Zips, and Generalised Castsで述べられている形を使って導出されているからである。(この論文の7.4節では、使われる複数のTypeable
について述べられているが、ライブラリが提供するのはTypeable1
からTypeable7
までだけである)。この条件を満たさない場合、プログラマは、そのデータ構築子に合った種を持つTypeableX
を書き、そのデータ型についてのインスタンスを手で書くことができる。
-XDeriveGeneric
を使うと、Generic
クラスのインスタンスを自動導出できる。このクラスはGHC.Generics
で定義されている。7.20. 総称プログラミングにあるように、これらを使って総称的な関数を定義することができる。
-XDeriveFunctor
を使うと、Functor
クラスのインスタンスを自動導出できる。このクラスはGHC.Base
で定義されている。
-XDeriveFoldable
を使うと、Foldable
クラスのインスタンスを自動導出できる。このクラスはData.Foldable
で定義されている。
-XDeriveTraversable
を使うと、Traversable
クラスのインスタンスを自動導出できる。このクラスはData.Traversable
で定義されている。
どの場合でも、クラスをderiving
節で言及する場合には、それがスコープになければならない。
newtype
を使って抽象型を定義するとき、元の型からインスタンスをいくつか継承させたいと思うことがあるかもしれない。Haskell 98では、Eq
、Ord
、Enum
、Bounded
に関しては自動導出を使うことでインスタンスを継承させられるが、それ以外の型については明示的なインスタンス宣言を書かなければならない。例えば、次のように定義したとする。
newtype Dollars = Dollars Int
そして、Dollars
に対して数値演算を使いたいなら、次のようにして明示的にNum
のインスタンスを書かなければならない。
instance Num Dollars where Dollars a + Dollars b = Dollars (a+b) ...
このインスタンスは、newtype
の構築子を適用したり排除したりしているだけである。特にくやしいのは、この構築子は実行時には存在しないので、このインスタンス宣言が定義する辞書はInt
の辞書と全体を通して同じで、違いはこちらの方がより遅いことだけだ、という点である。
このようなことをしなくても、GHCでは、-XGeneralizedNewtypeDeriving
を使ってこの種のインスタンスを自動導出できるようになった。従って、次のように書くことができる。
newtype Dollars = Dollars Int deriving (Eq,Show,Num)
そして、実装では、Dollars
についてInt
のものと同じNum
用辞書が使われる。概念上は、コンパイラは次のような形のインスタンス宣言を導出している。
instance Num Int => Num Dollars
このインスタンスは、型にしたがってnewtype
構築子を付けたり外したりすることだけを行う。
構築子クラスのインスタンスも同様に自動導出することができる。例えば、次のように、状態と失敗を扱うモナド変換子を実装したとしよう。
instance Monad m => Monad (State s m) instance Monad m => Monad (Failure m)
Haskell 98では、次のようにしてパーサモナドを定義することができる。
type Parser tok m a = State [tok] (Failure m) a
これは、上記のインスタンス宣言によって自動的にモナドになっている。拡張を使うと、Monad
クラスのインスタンスを書くことなくパーサの型を抽象型にすることができる。次のようになる。
newtype Parser tok m a = Parser (State [tok] (Failure m) a) deriving Monad
この場合、導出されたインスタンス宣言は次の形になる。
instance Monad (State [tok] (Failure m)) => Monad (Parser tok m)
Monad
は構築子クラスなので、このインスタンスはこのnewtypeの部分適用であり、左辺全体ではない。インスタンス宣言の文脈を生成するためにこの型宣言が「η変換」されたと考えることができる。
多引数型クラスについても、そのnewtypeがクラスの最後の引数であることを条件に、インスタンスを自動導出できる。この場合、deriving
節に現れるのはクラスの「部分適用」形である。例として、次のようなクラスがあったとする。
class StateMonad s m | m -> s where ... instance Monad m => StateMonad s (State s m) where ...
このとき、次のようにして、Parser
についてのStateMonad
のインスタンスを自動導出できる。
newtype Parser tok m a = Parser (State [tok] (Failure m) a) deriving (Monad, StateMonad [tok])
導出インスタンスは、部分適用されたクラスを最後にnewtypeに適用し、完全に適用された形にすることで得られる。次のようにである。
instance StateMonad [tok] (State [tok] (Failure m)) => StateMonad [tok] (Parser tok m)
この拡張の結果、newtype宣言での導出インスタンスは、Show
とRead
(これらは元の型とは異なる振る舞いをする)を除いた全てのクラスについて一様に扱われ(、元となった型の辞書を再利用することで実装され)る。
自動導出によってインスタンス宣言が構築されるときの規則は以下である。次の宣言(型シノニムを展開した後のもの)を考える。
newtype T v1...vn = T' (t vk+1...vn) deriving (c1...cm)
ここで、
ci
はクラスの部分適用であり、C t1'...tj'
という形をとる。ただしC
の引数の数はj+1
である。すなわち、C
はただ一つの引数を欠いている。
k
は、ci (T v1...vk)
の種が正しくなるように選ばれる。
型t
は任意の型である。
型変数vk+1...vn
は、t
にも、ci
にも出現しない。
ci
はどれも、Read
、Show
、Typeable
、Data
のいずれでもない。これらのクラスは型やその構築子を「素通りする」べきものではない。これらのクラスをnewtypeに自動導出することもできるが、それは通常の方法で行われ、この新しい機構は利用されない。
このとき、それぞれのci
に対して、自動導出されるインスタンス宣言は以下のようになる。
instance ci t => ci (T v1...vk)
うまくいかない例として、以下のものを考えてみよう。
newtype NonMonad m s = NonMonad (State s m s) deriving Monad
ここで、次のインスタンスを自動導出することはできない。
instance Monad (State s m) => Monad (NonMonad m)
なぜなら、型変数s
がState s m
に現れており、「η変換」によって消し去ることができないからである。このderiving
節が拒絶されるのは良いことである。NonMonad m
は実際、同じ理由で、モナドではないからである。正しい型を持つ>>=
を定義しようとしてみよ。できないはずである。
また、クラス引数の順序が重要になることに注意。これは、最後の引数についてしかインスタンスの自動導出ができないからである。もし上記のStateMonad
が次のように定義されていたなら、上のようにParser
型についてインスタンスを自動導出することは不可能だった。
class StateMonad m s | m -> s where ...
我々は、多引数型クラスには通常ひとつの「主要な」引数があり、それについてのインスタンス自動導出が最も重視される、という仮説を立てている。
最後に、これら全てはRead
、Show
、Typeable
、Data
の各クラスには適用されない。これらには、組込みの導出規則(Haskellレポートの4.3.3節)が適用される。(Eq
、Ord
、Ix
、Bounded
の各標準クラスについては、どちらの方法を使っても関係ない)