お問い合わせ

技術

FunctorとApplicative、Monadを理解する

2023.03.30

著者:井上 健太

はじめに

関数型言語における型クラスであるFunctorApplicativeMonadについて解説します。 これらは型を引数に1つ取る型を1つ引数に取る型クラスであり、これらを応用することによりエラー処理が楽になる等、活用の幅が広くあります。 そこで本記事ではFunctor等の特徴についてまとめます。

まずは型を引数に取る型について解説します。





型を引数に取る型

型を引数に取る型として、1引数ならリストやMaybe/Option型、多引数なら直和型や直積型があります。多引数の場合はCurry化すれば1引数とみなせるので、本記事では1引数の型を引数に取る型として考えます。

ここで、1引数の型を取る型を1つをmとします。 任意の型Aに対しm Aは型となりますが、多くの場合、元の型Aと何か関係付けられるものになっています。 例えばmがMaybe/Optionや直和などの場合、A -> m Aの(自然な)単射が存在し、m AAを含んでいる、すなわち A m Aのように考えられます。

このような型Am Aの間の関係を利用すると、A型の引数をとる関数fからm A型の引数を取る関数を自然に定義できる場合があります。例えばmMaybeの場合は入力がJust xだった場合、Just (f x)を、Nothingだった場合はそのままNothingを返す関数などです。

このようなA型を引数に取る関数から自然に定義されるm A型の引数を取る関数を簡単に使用できる仕組みがFunctorApplicativeMonadです。

それぞれの特徴を見ていきましょう。





Functor

Functorは1引数の型を取る型mに対し、主に任意の型A,Bに対するA -> Bの関数からm A -> m Bの関数を作る型クラスです。

実際にhaskellではFunctor型クラスには以下のようなfmap :: (a -> b) -> a -> bが定義されています。

class functor f where fmap :: (a -> b) -> f a -> f b (<$>) :: Functor f => (a -> b) -> f a -> f b (<$>) = fmap

実際にf::A -> Bに対し、(<$>) fm A -> m B型の関数になります。 例えばmMaybe型の場合は前述の通り、f <$> Just xJust (f x)f <$> NothingNothingを返します。


Functorに必要な公理

型を引数に取る型mに対し、Functor mは以下の公理を満たすものでなければなりません。

  • 任意の型A, B, Cと関数f::A -> B, g::B -> Cx::m Aに対して、 g . f <$> x g <$> (f <$> x)
  • 任意の型Ax:m Aに対し、 id <$> x x

1つ目の公理は以下のような可換図式が成り立つことを表しています。

2番目の公理は恒等射に関するものです。


主なFunctor

haskellの実装を見ていきます。

Maybe

instance Functor Maybe where fmap _ Nothing = Nothing fmap f (Just a) = Just (f a)

Either(直和)型

instance Functor (Either a) where fmap _ (Left x) = Left x fmap f (Right y) = Right (f y)

[](リスト)型

instance Functor [] where fmap = map

(,)(直積)型

instance Functor ((,) a) where fmap f (x,y) = (x,f y)




Applicative

Applicativeの用途は主に任意の型A, B, Cに対し、A -> B -> Cなどの多引数関数からm A -> m B -> m C型等の関数を作るものです。

これはFunctor型クラスだけでは実現できないので、別の型クラスApplicativeが必要になります。 実際の定義を見てみましょう。

class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b (<*>) = liftA2 id liftA2 :: (a -> b -> c) -> f a -> f b -> f c liftA2 f x = (<*>) (fmap f x)

(<*>)liftA2は相互定義になっていて、どちらかを与えればもう片方が自動的に定義されます。

liftA2は2引数ですが、それ以上の引数の場合も定義できます。

liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d liftA3 f a b c = liftA2 f a b <*> c

Applicativeに必要な公理

型を引数に取る型mに対し、Applicative mは以下の公理を満たすものでなければなりません。

  • 任意の型Av::m Aに対し、pure id <*> v v
  • 任意の型A, B, C, D, f::m (B -> C), g::m (A -> B), x::m Aに対し、pure (.) <*> f <*> g <*> x f <*> (g <*> x)
  • 任意の型A, B, f::A -> B, x::Aに対し、pure f <*> pure x pure (f x)
  • 任意の型A, B, f::m (A -> B), x::Aに対し、f <*> pure x pure ($ x) <*> f

1つ目の公理ですが、これはApplicativeからFunctorが作れることを表しています。 実際、任意の型A, B, f:A -> Bx:m Aに対し、f <$> xpure f <*> xと定義すればこれはまさにFunctorの公理の1つ目そのものです。 Functorのもう一つの公理はApplicativeの2番目と3番目の公理で実現できます。

2番目の公理は少しわかりづらいですが、liftA3を用いて書き直すと次のような結合則を表していることが分かります。liftA3 (.) f g x f <*> (g <*> x)

3番目の公理はApplicative則というよりはpure<$>の以下のような可換図式を表しています。

4番目の公理は変数の入れ替えです。


主なApplicative

Maybe

instance Applicative Maybe where pure = Just Just f <*> m = fmap f m Nothing <*> _m = Nothing

Either(直和)型

instance Applicative (Either e) where pure = Right Left e <*> _ = Left e Right f <*> r = fmap f r

[](リスト)型

instance Applicative [] where pure x = [x] fs <*> xs = [ f x | f <- fs, x <- xs]

ライブラリ上ではこの定義だが、実は以下の定義でもApplicative則が成り立つ。

instance Applicative [] where pure x = [x] fs <*> xs = [ f x | x <- xs, f <- fs]

この定義だと一般に<*>の出力が異なるが、後述するMonadを作る時の仕様を満たさなくなる。

(,)直積型

instance Monoid a => Applicative ((,) a) where pure x = (mempty, x) (u, f) <*> (v, x) = (u <> v, f x)

aはモノイドである必要があり、単位元memptyと演算<>を使います。





Monad

Monadは主に任意の型A, Bに対し、A -> m Bからm A -> m Bを作るためのクラスです。

これはApplicativeFunctorのみから作ることができないため、以下の型クラスを用います。

class Applicative m => Monad m where (>>=) :: forall a b. m a -> (a -> m b) -> m b return :: a -> m a return = pure

MonadApplicativeを継承していますが、注意が必要なのは実はMonadからApplicativeを作る方法が複数あることです。


Applicativeの継承に関する注意

Monad mから 例えばMonad mがあったとして、このmから以下の2通りのApplicativeが作れます。

class Monad m => VApplicative m where vpure :: a -> m a vpure = pure (<*.>) :: m (a -> b) -> m a -> m b f <*.> x = f >>= (<$> x) class Monad m => HApplicative m where hpure :: a -> m a hpure = pure (<*..>) :: m (a -> b) -> m a -> m b f <*..> x = x >>= \y -> ($ y) <$> f

さらにこの2つは実行結果が一般に異なります。

instance VApplicative [] where instance HApplicative [] where fs :: [Int -> Int] fs = [\n -> n + 1,\n -> n + 2] ns :: [Int] ns = [30,40] main :: IO () main = do print $ fs <*> ns -- [31,41,32,42] print $ fs <*.> ns -- [31,41,32,42] print $ fs <*..> ns -- [31,32,41,42]

この2つですが、haskellでは前者の定義(正確にはf <*> x $=$ f >>= (\y -> x >>= \z -> return (y z)))を満たすものでなければなりません。

これを踏まえながらMonadの以下の公理を確認していきます。


Monadに必要な公理

型を引数に取る型mに対し、Monad mは以下の公理を満たすものでなければなりません。

  • 任意の型A, B、任意のx::A, f::A -> m Bに対し、return x >>= f $=$ f x
  • 任意の型A、任意のx::m Aに対し、x >>= return $=$ x
  • 任意の型A, B, C、任意のx::m A, f::A -> m B, g::B -> m Cに対し、x >>= f >>= g $=$ x >>= (\y -> f y >>= g)

1,2つ目はreturnに関する公理で、それぞれ>>=の左右に渡したときの性質です。 3つ目は>>=の結合則に関するものになっています。


主なMonad

Maybe

instance Monad Maybe where (Just x) >>= k = k x Nothing >>= _ = Nothing

Either(直和)型

instance Monad (Either e) where Left l >>= _ = Left l Right r >>= k = k r

[]リスト型

instance Monad [] where xs >>= f = [y | x <- xs, y <- f x]

(,)直積型

instance Monoid a => Monad ((,) a) where (u,a) >>= k = case k a of (v,b) -> (u <> v,b)

aはモノイドである必要があり、演算<>を使います。





まとめ

これら3つを簡単にまとめると以下のようになります。

  • Functor m : A -> Bからm A -> m Bを作る。
  • Applicative m : A -> B -> Cからm A -> m B -> m Cを作る。
  • Monad m : A -> m Bからm A -> m Bを作る。 (任意の型A, B, C)

Monadは上2つを継承しているので、Monadであればこれら全てが使えます。 これらを用いて型を返す型mの世界で元の世界の関数が使えるようになります。





終わりに

FunctorApplicativeMonadについて、その特徴についてまとめました。


一覧に戻る


採用情報

Recruit


“技術で世界に勝負をかけて未来を拓く”
当社はこのような志で設立され、業界のリーディングカンパニーを目指して努力を続けています。
若い会社であるため、入社した社員全員で歴史を作り上げていくことを実感できることでしょう。
現在、採用活動を積極的に展開中です。
技術で世界に勝負をかける日本インサイトテクノロジーで、存分にお力を発揮してください。


新卒採用情報はこちら
キャリア採用情報はこちら


お問い合わせ

Contact


お問い合わせはこちら