このブログ記事では、Haskellの重要な型クラス、Applicativeを紹介します。

Applicative ファンクター
Applicative ファンクターは、ファンクターのアップグレード版です。ファンクターについて自信がない場合は、前回の記事、Road to Haskeller #13 - Functorsを確認してください。
fmap
を、たとえば2つのパラメータを必要とする関数に対して使用することを考えてみましょう。例えば、fmap (*) (Just 3)
です。どうなるでしょうか?
これは、Just
内に部分関数を作成します。例えば、Just (*3)
のようなものです。この部分関数は、さらに fmap
に渡すことができます。
ghci> :t fmap (*) (Just 3)
fmap (*) (Just 3) :: Num a => Maybe (a -> a)
ghci> let b = fmap (*) (Just 3)
ghci> fmap (\f -> f 9) b
Just 27
しかし、fmap (\f -> f (Just 9)) b
を行うと、これは機能しません。なぜなら、fmap
はファンクターの内部の型と同じ型の関数にのみ適用されるからです。
ファンクターの上の関数を他のファンクターに適用できるようにするために、Applicative
型クラスが Control.Applicative
モジュールで定義されています。
このクラスは以下のように定義されています。
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
最初の部分、(Functor f) => Applicative
は、f
が Applicative
になるためにまずファンクターである必要があることを意味します。
したがって、Applicative
型クラスのすべての型コンストラクタはファンクターでもあり、fmap
を使用できます。pure
は簡単で、入力値を持つ Applicative
ファンクターを作成します。
<*>
は興味深い関数で、内部に関数を持つファンクターを取り、その関数を別のファンクターに適用します。
これは、<*>
を使用して次のようなことができることを意味します。
ghci> (<*>) (Just (*3)) (Just 9)
Just 27
ghci> Just (*3) <*> Just 9
Just 27
Maybe
が Applicative
の有効なインスタンスであることを見てみましょう。
instance Applicative Maybe where
pure = Just
Nothing <*> _ = Nothing
(Just f) <*> something = fmap f something
pure
が部分関数適用でJust
の値を作るよう定義されています。<*>
では、Nothing
が提供された場合は操作を回避し、
Just
から関数を抽出し、それを fmap
に渡すように定義されています。これらの関数を使用すると、次のような興味深いことが実行できます。
ghci> pure (+) <*> Just 3 <*> Just 5
Just 8
ghci> Just (*) <*> Just 3 <*> Just 5
Just 15
Applicative
のセットアップを使用することで、複数のパラメータを持つ関数を applicative ファンクターの値に使用して部分関数や出力値を生成できます。
pure
の代わりに、<$>
を使用して fmap を実行し、次の操作のためのファンクターを作成することもできます。
ghci> (+) <$> Just 3 <*> Just 5
Just 8
上記は fmap (+) (Just 3)
を実行して Just (+ 3)
を作成し、<*>
を使用して Maybe
内の操作を実行しています。
リスト
リストも Applicative
型クラスの有効なインスタンスであり、次のように実装されています。
instance Applicative [] where
pure x = [x]
fs <*> xs = [f x | f <- fs, x <- xs]
上記から、リスト内包表記が <*>
の定義に利用されていることがわかります。リスト内包表記に自信がない場合は、
Road to Haskeller #3 - Lists & Tuplesを確認してください。Maybe
と同様に、次のようなことができます。
ghci> [(*0),(+100),(^2)] <*> [1,2,3]
[0,0,0,101,102,103,1,4,9]
上記からわかるように、<*>
は左側のリストを反復処理し、右側のリストの各要素に対して操作を実行します。
以下の例を見ると、操作の順序をよりよく理解できます。
[(+),(*)] <*> [1,2] <*> [3,4]
--- 上記は次のように変換されます:
--- [(+1), (+2), (*1), (*2)] <*> [3, 4]
--- [(3+1), (4+1), (3+2), (4+2), (3*1), (4*1), (3*2), (4*2)]
--- 結果: [4, 5, 5, 6, 3, 4, 6, 8]
<$>
を使用して、次のようなこともできます。
filter (>50) $ (*) <$> [2,5,10] <*> [8,10,11]
--- 上記は次のように変換されます:
--- filter (>50) $ [(*2), (*5), (*10)] <*> [8,10,11]
--- filter (>50) [(8*2), (10*2), (11*2), (8*5), (10*5), (11*5), (8*10), (10*10), (11*10)]
--- filter (>50) [16, 20, 22, 40, 50, 55, 80, 100, 110]
--- 結果: [55, 80, 100, 110]
IO
IO
も Applicative
の有効なインスタンスであり、次のようにインスタンスが実装されています。
instance Applicative IO where
pure = return
a <*> b = do
f <- a
x <- b
return (f x)
pure
関数は単に return
です。return
は何も行わない IO アクションを生成します。
<*>
関数は <-
構文を使用して IO アクションの出力を抽出し、内部操作を実行します。
次に、結果を return
でラップして IO アクションに戻します。これにより、次のようなリファクタリングが可能になります。
--- 書き換え前
myAction :: IO String
myAction = do
a <- getLine
b <- getLine
return $ a ++ b
--- 書き換え後
myAction = (++) <$> getLine <*> getLine
クイズ
この記事では、学習した内容を確認するためのクイズを設けます。記事のメイン部分を読んだ後に、ぜひ自分で問題を解いてみることを強くお勧めします。各問題をクリックすると答えが表示されます。
リソース
- NA. Functors, Applicative Functors and Monoids. Learn You a Haskell for Great Good.