Generic型クラスの利用
既に割と抽象的である「代数データ型」の抽象化の話なので、初見ではややこしいmrsekut.icon
よくある状況として、「データ型は自分で定義」して、「型クラスはLibraryが提供」したものを使う、というのがあるのでそれに沿ってメモっていく
これはわかりやすさのための状況設定でもあるので、実際の利用時はコレに限らない
自分が提供する型クラスの話に置き換えてもいい
登場人物
目的の型
これは自分で定義したもの
e.g. UserTree型
目的の型クラス
これはLibraryによって提供されるもの
上の「目的の型」をこの型クラスのinstanceにしたい状況下を考えている
e.g. Serialize型クラス
どういう状況か
今自分で定義したようなデータ型UserTree
code:hs
data UserTree a = Node a (UserTree a) (UserTree a) | Leaf
を、とあるLibraryが提供しているSerializeのinstanceにしたい
Genricを用いない時
自分でSerializeのinstanceを定義する必要がある
code:hs
instance Serialize UserTree where
...
...
今はUserTreeだけを考えているが、UserTree2、UserTree3のような型も出てきて、それらも全てSerializeのinstanceにしたい場合に、めんどい
逆に、今はSerializeだけを考えているが、Serialize2、Serialize3のような型クラスも出てきたときも同様にめんどい
Genricを用いている時
これは型クラスの提供者がGenricを用いている時という意味
これで目的が達成される
code:hs
{-# LANGUAGE DeriveGeneric #-} data UserTree a = Node a (UserTree a) (UserTree a) | Leaf deriving (Generic)
利用者がやることはこれだけ。
Serializeのinstanceにしたい側はめちゃくちゃ楽になる
deriving Genericと書けるようにするだけ
どうやるか
型クラスの提供者はいくつかの定義をしておく必要がある
GHC.Genericsが提供するV1や(:*:)のようないくつかの型を、Serializeのinstanceにしておく code:hs
instance Serialize V1 where
..
instance (Serialize f, Serialize g) => Serialize (f :+: g) where
..
ここでやっていることのイメージとしては
代数データ型をSerializeのinstanceにしている感じ
つまり任意の代数データ型に対してSerializeのinstanceを定義している
まとめると
Generic型クラスを利用することで、
型クラスの利用者はめちゃくちゃ楽になる
型クラスの提供者はちょっと大変になる
それと同じようなことを自分の提供する型クラスに対しても行える(Library目線)
ただし、通常はderiving Showと書くのに対し、
deriving Genericと書く
deriving Serializeではなく
ここの差は重要mrsekut.icon
deriving Genericとすることで、deriving Serializeと同様の効果が得られる
ということは、deriving Genericとすれば、
Serializeに限らず、Genericに対応した他の型クラスのinstanceにも自動でなっていることも意味する
つまり、あるdata型に対してderiving Genericすれば、「任意の「任意の代数データ型のinstanceにできる型クラス」」のinstanceにできる
Generic型クラスの嬉しさをそれぞれの立場で雑にまとめると
利用者→instanceの実装を省略できて嬉しい
提供者→自分の提供する型クラスをdervingできるようになって嬉しい
deribing Genericという記述を見たときに連想できること
例えば、LibraryのUsageに「deriving Genericと書いてね」とあった場合に何を意味するか
あるいは、コードリーディングしてたら、deriving Genericを見かけた場合に何を意味するか
これは以下のようなことを表す
そのLibraryが提供する(Genericに対応した)型クラスのinstanceにしようね
そのコードが利用しているLibraryの中の(Genericに対応した)何らかの型クラスのinstanceになっているよ