HaskellとScalaのMonad関連について学び始めた頃に以下のブログ記事を読みました。
Functors, Monads, Applicatives – can be so simple | de.velopmind | The Det about Programming
Functor, Applicative, Monadといえば様々な角度やレベルから説明が可能と思われ、実際に多くの書籍や記事、ドキュメントで多くのことが語られていますが、このブログ記事は本質的な機能を(タイトル通り)とてもシンプルに説明していました。
直感的に理解しやすいと感じていたので、この内容を参考に補足説明とサンプルコードを加えて改めて整理してみました。
※注意: シンプルに抽象化して統一的に理解することを目的としているため、Scala, Haskellの実際の言語仕様や実装レベルの詳細に正確には一致しない部分があります。
定式化
型パラメータを取る型Cの中身の型をAからBに変換するにはC[A] => C[B]という型の関数が必要となる。
型CがFunctor
, Applicative
, Monad
のインスタンスであれば、以下の3通りの方法で関数C[A] => C[B]を得ることができる。
1. 型クラスFunctor
のコア機能
(A => B) => (C[A] => C[B])
関数A => Bを関数C[A] => C[B]に変換する。
Scala | Haskell |
---|---|
map |
fmap , <$>
|
2. 型クラスApplicative
のコア機能
C[A => B] => (C[A] => C[B])
関数C[A => B]を関数C[A] => C[B]に変換する。
Scala (scalaz) | Haskell |
---|---|
ap , <*>
|
<*> |
3. 型クラスMonad
のコア機能
(A => C[B]) => (C[A] => C[B])
関数A => C[B]を関数C[A] => C[B]に変換する。
Scala | Haskell |
---|---|
flatMap |
>>= |
適用例
型Cの具体例としてScalaのOption
、HaskellのMaybe
を当てはめてみる。
1. A => Bという型の関数があるとき
型CがFunctor
のインスタンスであればC[A] => C[B]が得られる。
例えば、
(Int => String) => (Option[Int] => Option[String])
val f: Int => String = x => x.toString
val n: Option[Int] = Some(1)
// Option#map を利用すると
n.map(f)
// (OptionはMonadでもあるので) for式で書くと
for {
a <- n
} yield f(a)
f :: Int -> String
f = \x -> show x
n :: Maybe Int
n = Just 1
-- fmap を利用すると
fmap f n
-- <$> を利用すると
f <$> n
-- (MaybeはMonadでもあるので) do記法で書くと
do
a <- n
return $ f a
2. C[A => B]という型の関数があるとき
型CがApplicative
のインスタンスであればC[A] => C[B]が得られる。
例えば、
Option[Int => String] => (Option[Int] => Option[String])
val f: Option[Int => String] = Some(x => x.toString)
val n: Option[Int] = Some(1)
import scalaz._, Scalaz._
// scalaz.Apply.ap を利用すると
Apply[Option].ap(n)(f)
// scalaz.syntax.ApplyOps.<*> を利用すると
n <*> f
// (OptionはMonadでもあるので) for式で書くと
for {
a <- n
g <- f
} yield g(a)
f :: Maybe (Int -> String)
f = Just $ \x -> show x
n :: Maybe Int
n = Just 1
-- <*> を利用すると
f <*> n
-- (MaybeはMonadでもあるので) do記法で書くと
do
a <- n
g <- f
return $ g a
3. A => C[B]という型の関数があるとき
型CがMonad
のインスタンスであればC[A] => C[B]が得られる。
例えば、
(Int => Option[String]) => (Option[Int] => Option[String])
val f: Int => Option[String] = x => Some(x.toString)
val n: Option[Int] = Some(1)
// Option#flatMap を利用すると
n.flatMap(f)
// for式で書くと
for {
a <- n
b <- f(a)
} yield b
f :: Int -> Maybe String
f = \x -> Just $ show x
n :: Maybe Int
n = Just 1
-- >>= を利用すると
n >>= f
-- do記法で書くと
do
a <- n
f a
Further Reading
HaskellのFunctor, Applicative, Monadについて箱のイメージで説明したブログ記事
の翻訳です。
ここでいう「箱」が上述の型Cに相当します。