人気ブログランキング | 話題のタグを見る

Applicative クラス

Data.Tree モジュールで Applicative の情報を info で見ると、Tree a 型のデータに pure, <*>, *>, <* の四つの関数が使えることがわかる。<$> 関数がないが、これは pure <*> と同じだ。

Prelude Data.Tree> :info Applicative
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
-- Defined in ‘GHC.Base’
instance Applicative (Either e) -- Defined in ‘Data.Either’
instance Applicative Tree -- Defined in ‘Data.Tree’
instance Applicative [] -- Defined in ‘GHC.Base’
instance Applicative Maybe -- Defined in ‘GHC.Base’
instance Applicative IO -- Defined in ‘GHC.Base’
instance Applicative ((->) a) -- Defined in ‘GHC.Base’
instance Monoid a => Applicative ((,) a) -- Defined in ‘GHC.Base’

それではそれぞれの関数が Tree a 型のデータではどのように現れるかを見てみよう。まずは、pure 関数だ。これは Monad の return 関数と同じで生のデータをデータ型に包む働きがある。

まず生の自然数を pure 関数で Tree 型のデータに梱包してみよう。ただし、上の表示でわかるように pure 関数は Either e 型や、リスト型、Maybe 型、IO 型のように色々なデータ型に対応している。したがって自然数を pure 関数を使って Tree a 型にするには、:: Tree Int による型指定が必要になる。

Prelude Data.Tree> pure 1 :: Tree Int
Node {rootLabel = 1, subForest = []}

このように Tree a 型では pure 関数で rootLabel = 1 で子ノードのない木構造が返されることがわかる。Monad の return でモナドのデータを作ることができるのは生のデータだけだったが、Applicative の pure の場合は関数も Applicative 型に包み込むことができる。

Prelude Data.Tree> :t pure (*2)
pure (*2) :: (Num a, Applicative f) => f (a -> a)

Applicative に包まれた関数は <*> 演算子で Tree a 型のデータに関数適用できる。この場合 Tree a のコンテナのデータに Applicative に包まれた関数が関数適用される。

Prelude Data.Tree> let a = pure 1 :: Tree Int
Prelude Data.Tree> a
Node {rootLabel = 1, subForest = []}
Prelude Data.Tree> pure (*2) <*> a
Node {rootLabel = 2, subForest = []}

木構造のデータが子ノードを持っているときは (*2) はそれらのノード全てにも適用される。

Prelude Data.Tree> let b = Node 1 [Node 2 [], Node 3 []]
Prelude Data.Tree> b
Node {rootLabel = 1, subForest = [Node {rootLabel = 2, subForest = []},Node {rootLabel = 3, subForest = []}]}
Prelude Data.Tree> pure (*2) <*> b
Node {rootLabel = 2, subForest = [Node {rootLabel = 4, subForest = []},Node {rootLabel = 6, subForest = []}]}

Tree a のコンテナの値を2倍にするだけだったら fmap でもできる。

Prelude Data.Tree> fmap (*2) b
Node {rootLabel = 2, subForest = [Node {rootLabel = 4, subForest = []},Node {rootLabel = 6, subForest = []}]}

しかし Applicative なら2引数の関数の場合も適用できる。

Prelude Data.Tree> pure (+) <*> pure 10 <*> b
Node {rootLabel = 11, subForest = [Node {rootLabel = 12, subForest = []},Node {rootLabel = 13, subForest = []}]}

これには関数のカリー化が関係している。pure (+) を pure 10 に関数適用すると、1引数の関数(Applicative に梱包されたものだが)になる。

Prelude Data.Tree> :t pure (+) <*> pure 10
pure (+) <*> pure 10 :: (Num a, Applicative f) => f (a -> a)

したがって、これをさらに Tree a 型の b にカスケードに関数適用することによって、最初の Tree a 型のデータが得られる。

*> は Monad の時の >> と同じで左項の値は右項に渡されない。

Prelude Data.Tree> pure (*2) *> a
Node {rootLabel = 1, subForest = []}

<* は逆に右項の値が無視される。

Prelude Data.Tree> :t pure (+) <*> pure 10 <* a
pure (+) <*> pure 10 <* a :: Num a => Tree (a -> a)

このように上の結果は Tree a 型のデータではなく1引き数関数になるから、もう一回引数を与えると Tree a 型のデータになる。

Prelude Data.Tree> pure (+) <*> pure 10 <* a <*> b
Node {rootLabel = 11, subForest = [Node {rootLabel = 12, subForest = []},Node {rootLabel = 13, subForest = []}]}

最後に pure (+) の引数にどちらも Tree a 型を与えるとどうなるだろうか。

Prelude Data.Tree> pure (+) <*> b <*> b
Node {rootLabel = 2, subForest = [Node {rootLabel = 3, subForest = []},Node {rootLabel = 4, subForest = []},Node {rootLabel = 3, subForest = [Node {rootLabel = 4, subForest = []},Node {rootLabel = 5, subForest = []}]},Node {rootLabel = 4, subForest = [Node {rootLabel = 5, subForest = []},Node {rootLabel = 6, subForest = []}]}]}

なぜ、このような結果になるのかよくわからなかったが、これは次の関数 printTree を定義して適用すると分かりやすい。表示は省略する。

Prelude Data.Tree> let printTree = putStr . drawTree . (fmap show)
Prelude Data.Tree> printTree $ pure (+) <*> b <*> b

Data.Tree モジュールの木構造のデータ Tree a 型を活用するという目的の他にも、色々なタイプクラスの関数のふるまいを知るという目的にも Data.Tree モジュールの探索は有用だ。


by tnomura9 | 2016-10-16 22:57 | Haskell | Comments(0)
<< 箱入りデータ Data.Tree のタイプクラス >>