DerivingVia
Haskellの頻出パターンとして、型クラスのためのnewtypeがある。これをここではラッパーと呼ぶ。
例えば、SumはSemigroupのためのラッパーである。
code:Sum.hs
newtype Sum a = Sum { getSum :: a }
instance Num a => Semigroup (Sum a) where
(<>) = coerce ((+) :: a -> a -> a)
stimes n (Sum a) = Sum (fromIntegral n * a)
Intのような数値型をSemigroupとして扱いたいときは、代わりにSum Intを使うことで和演算についてのSemigroupとして扱える。
逆に、積演算についてのSemigroupとしてProductがある。
このように、同じ型について複数の型クラスインスタンスが考えられる時にラッパーは有用。
少し機械的な例だが、デフォルトで和演算のSemigroupを実装する型SIntを定義する。
code:SInt.hs
newtype SInt = SInt { getSInt :: Int }
deriving newtype (Num)
instance Semigroup SInt where
x <> y = getSum (Sum x <> Sum y)
Semigroup SIntの定義はいかにもボイラープレートっぽい。
code:SInt.hs
newtype SInt = SInt { getSInt :: Int }
deriving newtype (Num, Show)
deriving Semigroup via (Sum SInt)
show (SInt 0) = "SInt { getSInt = 0 }" stock
show (SInt 0) = "0" newtype
もっと複雑な例も見てみる
code:Program.hs
data Program a = Program
_topFuncs :: [(a, (a, Exp a))], }
deriving stock (Eq, Show, Functor, Generic)
このProgram型について、Semigroupを実装したいとする。
手で書くこともできる。
code:Program.hs
instance Semigroup (Program a) where
(Program tv tf ef) <> (Program tv' tf' ef') = Program (tv <> tv') (tf <> tf') (ef <> ef')
面倒だ。こんなときもDerivingViaが使える。Genericを利用してSemigroupを実装するGenericallyというラッパーがある。 code:Program.hs
data Program a = ...
deriving Semigroup via (Generically (Program a))
これで上の手書きインスタンスと等価なコードが得られる。パフォーマンスについてもほとんど変わりはない。
ラッパーはnewtypeなので実行時の変換コストはゼロ
Genericのコストはちょっとあるはず