PR

処理の成功と失敗を表現するAlternativeクラス

 Control.Applicativeモジュールでは,ほかに第29回で説明したMonoidクラスのApplicative版であるAlternativeクラスも提供されています。Alternativeクラスの定義は以下の通りです。

class Applicative f => Alternative f where
    -- | The identity of '<|>'
    empty :: f a
    -- | An associative binary operation
    (<|>) :: f a -> f a -> f a

    -- | One or more.
    some :: f a -> f [a]
    some v = some_v
      where
        many_v = some_v <|> pure []
        some_v = (:) <$> v <*> many_v

    -- | Zero or more.
    many :: f a -> f [a]
    many v = many_v
      where
        many_v = some_v <|> pure []
        some_v = (:) <$> v <*> many_v

 emtpyメソッドは,MonadPlusクラスのmzeroメソッドやMonoidクラスmemptyメソッドに相当します。<|>演算子は,MonadPlusクラスのmplusメソッドやMonoidクラスのmappendメソッドに相当するものです。したがって,Alternativeクラスに対するインスタンスは,以下の三つの法則を満たすように定義される必要があります(参考リンク1参考リンク2)。

  1. empty <|> x == x
  2. x <|> empty == x
  3. x <|> (y <|> z) == (x <|> y) <|> z

 Alternativeクラスでは,失敗する可能性のある処理を失敗するまで繰り返し行い,結果をリストにまとめて返すための二つのメソッドも提供されています。manyメソッドは,すべての処理が失敗した場合に空リストを返します。someメソッドは,すべての処理が失敗した場合に,空リストではなく,*empty*(失敗)という単独の値を結果としてリストに包んで返します(参考リンク)。これらのメソッドは,主にparsecパッケージattoparsecパッケージなどのパーサー・ライブラリで,パース結果を得るために使われます。

 someメソッドやmanyメソッドにはデフォルトの定義が用意されているため,Alternativeクラスのインスタンスを定義する際にこれらのメソッドを用意する必要はありません。ただし,このデフォルトの定義は,文字列を前から順に一文字ずつ失敗するまでパースしていくというパーサーのアクションのように,「何度も繰り返し適用することで,成功から失敗へと状態を遷移させる性質を持つような処理」を引数として渡すことを前提にした定義になっています。単純に成功を返すような処理では無限ループを引き起こすため,パーサー以外ではあまり役に立ちません。このことから,これらのメソッドの利用目的が明確になるよう,これらのメソッドをAlternativeクラスから別のクラスに移そうという提案もあります(参考リンク)。

Prelude Control.Applicative> some Nothing
Nothing
Prelude Control.Applicative> some $ Just ()
Interrupted.
Prelude Control.Applicative> many Nothing
Just []
Prelude Control.Applicative> many $ Just ()
Interrupted.
Prelude Control.Applicative> some []
[]
Prelude Control.Applicative> some [()]
Interrupted.
Prelude Control.Applicative> many []
[[]]
Prelude Control.Applicative> many [()]
Interrupted.

 Maybe型やリストは,someやmanyのデフォルトの定義が想定する「繰り返し何度も適用することで,成功から失敗へと状態を遷移させる性質を持つような処理」を表現するものではありません。また,AlternativeクラスのMaybe型やリストに対するインスタンスでは,someやmanyは定義されていません。このため,「Just ()」や「[()]」のような成功値を送った場合には,無限ループが発生しています。

 パーサーであっても,パーサーが行う処理の定義によっては,someやmanyのデフォルトの定義が,文字列のパースには非効率な場合もあります。こうした理由から,someやmanyはインスタンスに応じて再定義できるように,Alternativeクラスのメソッドとして定義されています。

 なお,someメソッドとmanyメソッドを自分で定義する場合には,これらのメソッドは以下の二つの法則を満たすように定義しなければなりません。

  1. some v = (:) <$> v <*> many v
  2. many v = some v <|> pure []

 1番目の法則では「someメソッドが1個以上の結果をリストとして返すこと」,2番目の法則では「manyメソッドでは計算が成功しない場合に空リストを返すこと」が求められています。

 今回はパーサーについて説明するのが目的ではないので,これ以上深入りはしません。とりあえず「Alternativeクラスがパーサーで利用するのを目的としてsomeメソッドとmanyメソッドを提供していること」「Alternativeクラスのインスタンスを定義する際にはこれらのメソッドを定義する必要はないこと」「これらのメソッドを定義する場合には守らなければならない法則があること」の三つを覚えておいてください。

 では,Alternativeクラスのインスタンスの定義を見てみましょう(参考リンク1参考リンク2)。

instance Alternative Maybe where
    empty = Nothing
    Nothing <|> r = r
    l       <|> _ = l

~ 略 ~
instance Alternative [] where
    empty = []
    (<|>) = (++)

~ 略 ~
instance Alternative STM where
    empty = retry
    (<|>) = orElse

 これらのインスタンスの定義は,MonadPlusクラスやMonoidクラスのインスタンスと同じものになっているのがわかります。