復習
Semigroup
class Semigroup a where
(<>) :: a -> a -> a
Monoid
- 結合則 (a・b)・c = a・(b・c)
- 単位元 e・a = a・e = a
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であるべき」宗派ではなく、「デフォルトの設定に対して変更関数を用意すべき」宗派なので、あしからず。