2018-03-06
あなたの知らないSemigroupの世界

自分で定義するデータの中には、足し算したくなるようなデータがある。たとえば、送信と受信のカウンターを定義したとしよう。
data Metrics = Metrics { rx :: Int , ts :: Int } deriving (Eq, Show)
これは以下のように足し算できると嬉しいだろう。
> Metrics 1 2 + Metrics 3 4 Metrics {rx = 4, ts = 6}
しかしこれは Num のインスタンスにすべきではない。このデータ型に掛け算は定義できないからだ。かといって、addMetrics みたいな関数を定義するのはかっこ悪い。
> etrics 1 2 `addMetrics` Metrics 3 4 Metrics {rx = 4, ts = 6}
このように演算子が一個だけ欲しいにと思ったら、それは多分 Monoid だ。
import Data.Monoid instance Monoid Metrics where mempty = Metrics 0 0 Metrics r1 t1 `mappend` Metrics r2 t2 = Metrics (r1 + r2) (t1 + t2)
GHC 7.10までは、(<>) が mappend の別名であるので、以下のようなコードが書ける。
> etrics 1 2 <> Metrics 3 4 Metrics {rx = 4, ts = 6}
やったね!
GHC 8.4へようこそ
上記のコードを GHC 8.4 で読み込むと以下のようなエラーが出る。
Example.hs:8:10: error: ・ No instance for (Semigroup Metrics) arising from the superclasses of an instance declaration ・In the instance declaration for ‘Monoid Metrics’ | 8 | instance Monoid Metrics where | ^^^^^^^^^^^^^^
これはどういうことだろう? その疑問に答えるのがこの記事の主旨である。
mappendよりも(<>)の方がかっこいいのに、長い間 (<>) はMonoidのメソッドにはしてもらえなかった。あくまで別名であった。それは一部の人に、SemigroupをMonoidのスーパークラスにするという野望があったからだ。
数学での定義を思い出そう:
半群 (Semigroup)
- 結合則: (a <> b) <> c = a <> (b <> c)
モノイド (Monoid)
群 (Group)
さっきの疑問に答えると、GHC 8.4ではSemigroupがMonoidのスーパークラスとなり、Metricsに対する(<>)の定義がないために、エラーが出たという訳だ。
状況把握
今後どのようなコードを書けばよいかという疑問に答えるためには、GHCの各バージョンでの状況を把握しなければならない。
GHC 7.10 (base 4.8)
GHC 7.10 では、みなさんご存知のように base パッケージに Data.Monoid モジュールがある:
-- base : Data.Monoid class Monoid a where mempty :: a mappend :: a -> a -> a (<>) :: a -> a -> a (<>) = mappend
Monoid型自体はPreludeの仲間入りを果たしたが、(<>)は明示的にimportする必要がある。
Data.Semigroupは、semigroupsパッケージで定義されている:
-- semigroup : Data.Semigroup class Semigroup a where (<>) :: a -> a -> a default (<>) :: Monoid a => a -> a -> a (<>) = mappend
最後の default は、DefaultSignatures という拡張で、Monoidの制約を持てば Semigroupの方の (<>) は mappend で代用できると読む。親子関係がひっくり返っていて、なんだかなぁという感じである。
GHC 8.0 (base 4.9)
Data.Semigroupがsemigroupパッケージからbaseパッケージへ移った:
-- base : Data.Monoid class Monoid a where mempty :: a mappend :: a -> a -> a (<>) :: a -> a -> a (<>) = mappend --base : Data.Semigroup class Semigroup a where (<>) :: a -> a -> a
親子関係はない。
フラグ -Wnoncanonical-monoid-instances が定義された。これは、MonoidのインスタンスなのにSemigroupのインスタンスになってないと警告を出すフラグである。デフォルトは off。上位互換性に関するフラグ -Wcompat を付けても、警告が出る。
まだ GHC 8.4 を使えない人は、-Wall の横に -Wcompat を書き足して遊んでみるとよい。
GHC 8.2 (base 4.10)
何も変更なし。嵐の前の静けさだ。
GHC 8.4 (base 4.11)
なんとなんと、MonoidとSemigroupがPreludeの仲間に入った。そして、SemigroupがMonoidのスーパークラスとなった。
-- Prelude class Semigroup a where (<>) :: a -> a -> a class Semigroup a => Monoid a where mempty :: a
フラグ -Wnoncanonical-monoid-instances でデフォルトで on となった。嵐がやってきたのだ。
対処方法
ここまで解説すれば、対処方法は明らかであろう。Semigroup (as superclass of) Monoid Proposalの最後に、semigroupsパッケージを使う方法と使わない方法が載っているので、よく眺めてほしい。
- 7 https://www.google.co.jp/
- 6 http://www.google.co.uk/url?sa=t&source=web&cd=1
- 2 http://b.hatena.ne.jp/
- 2 http://b.hatena.ne.jp/entrylist/it/技術ブログ
- 2 http://b.hatena.ne.jp/entrylist?sort=hot
- 2 http://b.hatena.ne.jp/murashit/favorite
- 1 http://a.hatena.ne.jp/takatoh/
- 1 http://b.hatena.ne.jp/ctop/it
- 1 http://b.hatena.ne.jp/entry/s/irnote.com/n/na8b9dd20e449
- 1 http://b.hatena.ne.jp/search/text?q=tls