復習
Semigroup
- 結合則 (a・b)・c = a・(b・c)
class Semigroup a where (<>) :: a -> a -> a
Monoid
class Semigroup a => Monoid a where mempty :: a mappend :: a -> a -> a mappend = (<>)
本題
Haskellerの中には、「設定はMonoidであるべき」宗派が存在する。そのような信念を持つHaskellerが作ったライブラリを使おうとすると、Monoid のインスタンスを (<>)
でつないで設定データを構築することになる。
この記事では、SemigroupをMonoidに昇格させるのはいつかという話題を扱うので、まずSemigroupである設定データから始めよう。僕が最近秀逸だと思っているフラグを例として挙げる。
フラグには以下のような操作ができる:
- フラグをセットする (
FlagSet
) - フラグをクリア(アンセット)する (
FlagClear
) - フラグをデフォルトに戻す (
FlagReset
)
これを実装するのは簡単だろう。(<>)
の右が勝つことに決めれば、以下のようになる。
data FlagOp = FlagSet | FlagClear | FlagReset deriving (Eq,Show) instance Semigroup FlagOp where _ <> op = op
使ってみよう:
> FlagSet <> FlagReset <> FlagClear FlagClear
あとは、これを受け取って、
FlagSet
ならフラグをセットするFlagClear
ならフラグをクリアするFlagReset
ならデフォルトの値を使用する
という関数を書くことになるだろう。そこは割愛する。
これで話が終われば、設定データは Semigroup で十分で Monoid にする必要はない。どうして、設定と言えば Monoid と言われるのだろう?
答えは「設定したい項目は複数あることが多い」からだ。FlagOp
を複数格納しているデータをConfig
としよう。
data Config = Config { aflag :: FlagOp, bflag :: FlagOp } deriving (Eq,Show) instance Semigroup Config where Config a1 b1 <> Config a2 b2 = Config (a1 <> a2) (b1 <> b2)
aflag
をセットする setAFlag
を作るとする。bflag
は明らかに FlagSet
や FlagClear
ではないので、FlagReset
としてみる。
setAFlag :: Config setAFlag = Config { aflag = FlagSet, bflag = FlagReset }
同様に、setBFlag
も作る。
setBFlag :: Config setBFlag = Config { aflag = FlagReset, bflag = FlagSet }
setAFlag
と setBFlag
を結合するとどうなるだろう?
> setAFlag <> setBFlag Config {aflag = FlagReset, bflag = FlagSet}
あれれれれ? aflag
も bflag
も FlagSet
であるべきなのに、aflag
は FlagReset
になってしまった。
どうやって直すべきだろうか? 元の値を保存する FlagKeep
が必要そうだ。
元の値を保存する? それって、単位元では? ということは、Monoid に昇格するべきでは?
その通りだ。
data FlagOp = FlagSet | FlagClear | FlagReset | FlagKeep deriving (Eq,Show) instance Semigroup FlagOp where op <> FlagKeep = op _ <> op = op instance Monoid FlagOp where mempty = FlagKeep data Config = Config { aflag :: FlagOp, bflag :: FlagOp } deriving (Eq,Show) instance Semigroup Config where Config a1 b1 <> Config a2 b2 = Config (a1 <> a2) (b1 <> b2) instance Monoid Config where mempty = Config mempty mempty setAFlag :: Config setAFlag = Config { aflag = FlagSet, bflag = mempty } setBFlag :: Config setBFlag = Config { aflag = mempty, bflag = FlagSet }
使ってみよう。
> setAFlag <> setBFlag Config {aflag = FlagSet, bflag = FlagSet}
めでたし、めでたし。
なお、僕は「設定はMonoidであるべき」宗派ではなく、「デフォルトの設定に対して変更関数を用意すべき」宗派なので、あしからず。