第3回でFunctorクラスとMonadクラスを紹介しましたが,ここ数年の間に,FunctorクラスとMonadクラスの関係に少し変化が起こりました。Functorクラスよりも強く,Monadクラスよりも弱いクラス,すなわちFunctorクラスとMonadクラスの中間に相当するApplicativeクラスが現れ,広く使われるようになったのです。特にパーサー・ライブラリでは,Applicativeクラスおよび「Applicative版のMonoid」であるAlternativeクラスが欠かせないものになっています。
第48回でのparTraversable関数の説明,および第50回で取り上げたparMap関数/parMapM関数の定義の文脈部分で登場したTraversableクラスも,Applicativeとのかかわりが深いクラスです。
Applicativeに対する理解は,今やHaskellで実用的なプログラムを書くうえでは避けて通れません。今回は,Applicativeクラスについて詳しく説明していきます。
Functorに追加される関数
Applicativeクラスの追加に伴って,Functorクラスの定義も少し変化しました。Applicativeクラスを説明する前に,Functorクラスの変化について説明しておきましょう。
Data.Functorモジュールでは,fmapメソッドの別名として<$>演算子が提供されています。
-- | An infix synonym for 'fmap'. (<$>) :: Functor f => (a -> b) -> f a -> f b (<$>) = fmap
また,Functorクラスのメソッドとして<$演算子が追加され,Data.Functorでエクスポートされています。
class Functor f where fmap :: (a -> b) -> f a -> f b -- | Replace all locations in the input with the same value. -- The default definition is @'fmap' . 'const'@, but this may be -- overridden with a more efficient version. (<$) :: a -> f b -> f a (<$) = fmap . const
Functorクラスでは<$演算子に対しデフォルトの定義を与えているので,Functorクラスのインスタンスで<$演算子を定義する必要はありません。したがって,Functorクラスに対する<$演算子の追加が,既存のFunctorクラスのインスタンスや,新たにFunctorクラスのインスタンスを定義する際に影響を及ぼすことはありません。ただし,Functorクラスのインスタンスで<$演算子の効率的な実装を提供できるのであれば,<$演算子のデフォルトの定義を上書きしてもかまいません。
<$演算子は,Functorクラスのインスタンスとして定義されたStateやIOのようなコンテナの中の値を,第1引数で与えられた値に置き換えて返します。
Prelude Data.Functor> 11 <$ return "string" 11 Prelude Data.Functor> 11 <$ print "string" "string" 11 Prelude Data.Functor> :m +Control.Monad.State Prelude Data.Functor Control.Monad.State> flip runState "e" (11 <$ put "string") (11,"string")
1番目と2番目の例はIOというコンテナの例で,3番目は第6回で説明したStateモナドのStateというコンテナの例です。Stateモナドでは,put関数を使って「状態」を更新します。このとき「put "string"」でStateというコンテナの「状態」はstringという文字列になりますが,コンテナの値は()になります。
Prelude Data.Functor Control.Monad.State> flip runState "e" (put "string") ((),"string")
このコンテナの値を,<$演算子を使って()から11に置き換えているのです。
<$演算子で置き換えられるのはコンテナの中の値だけで,第2引数として渡した「print "string"」や「put "string"」という処理自体には影響しないことに注意してください。
<$演算子は,複数の計算から一つの大きな計算を組み立てる際,組み立てる計算の中に何らかの作用を持つ計算を組み込むのに役立ちます。
Prelude Data.Functor> (* 11) 11 <$ print "string" "string" 121 Prelude Data.Functor> :m +Control.Monad Prelude Data.Functor Control.Monad> ((*) <$>) (11 <$ print "string") `ap` return 11 "string" 121 Prelude Data.Functor Control.Monad> ((*) <$>) (return 11) `ap` (11 <$ print "string") "string" 121
この例では,複数の計算から一つの大きな計算を組み立てるのに,Monadクラスのreturnメソッドや,第17回で説明したControl.Monadモジュールのap関数を利用しています。Functorクラスが提供するメソッドには,liftM関数に相当する関数の持ち上げ機能がありますが,持ち上げた関数に適切な引数を渡す機能はメソッドとして提供されていません。このため,関数を適用できるコンテナに値を格納するためにreturnメソッドを利用したり,<$>演算子で持ち上げた関数を「コンテナに包まれた値」に適用するためにap関数を使ったりしているのです。
Prelude Control.Monad> :t ap ap :: Monad m => m (a -> b) -> m a -> m b
Functorクラス単体の能力はとても弱いため,複数の計算からなる一つの大きな計算を組み立てるには,このようにMonadなどの助けを借りる必要があります。