Upgrade to Pro — share decks privately, control downloads, hide ads and more …

モナドと出会う

 モナドと出会う

社内発表資料

Avatar for わんこ(Wanko_IT)

わんこ(Wanko_IT)

August 28, 2026

More Decks by わんこ(Wanko_IT)

Other Decks in Programming

Transcript

  1. モナドの3つの抽象度 圏論で⽣まれた概念が、関数型プログラミングに輸⼊された 抽象 1 2 3 具体 圏論のモナド 対象外 数学における元々の定義

    関数プログラミング全般のモナド 本発表の対象 ⾔語をまたぐ共通のインターフェース 特定の関数⾔語のモナド Haskell / Scala / Clojure など各⾔語での実装 本発表の対象
  2. 各⾔語の定義 ── Haskell Control.Monad · 型クラスで定義 class Applicative m =>

    Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a return = pure -- return と pure は同じ class Functor f => Applicative f where pure :: a -> f a class Functor f where fmap :: (a -> b) -> (f a -> f b) (>>=) ── bind return / pure fmap ⽂脈つきの値 m aの中⾝を取り出し、第 ⼆引数の関数に渡す 値aに m の⽂脈を付与して返す 関数 a -> bを f a -> f bに変換する
  3. 各⾔語の定義 ── Scala cats.Monad · trait の継承で定義 trait Monad[F[_]] extends

    FlatMap[F] with Applicative[F] trait FlatMap[F[_]] extends Apply[F] … { def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] } trait Applicative[F[_]] extends Apply[F] … { self => def pure[A](x: A): F[A] } trait Apply[F[_]] extends Functor[F] … trait Functor [F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] } flatMap = bind (>>=) pure = return map = fmap ⽂脈つきの値 F[A]の中⾝を取り出 し、第⼆引数の関数に渡す 値 AにFの⽂脈を付与する F[A]にA => Bを適用してF[B]を得る (F[A] => F[B]の関数と見れる)
  4. 各⾔語の定義 ── Clojure algo.monads · 動的型付けの変わり種 マクロで表現 型クラスも trait もないが、マクロで同じ構造を作る

    (defmacro monad [operations] `(let [~'m-bind ::this-monad-does-not-define-m-bind ~'m-result ::this-monad-does-not-define-m-result ~'m-zero ::this-monad-does-not-define-m-zero ~'m-plus ::this-monad-does-not-define-m-plus ~@operations] {:m-result ~'m-result :m-bind ~'m-bind :m-zero ~'m-zero :m-plus ~'m-plus})) 実体はただのマップ :m-bind と :m-result に関数が紐づく (defmonadfn m-fmap [f m] (m-bind m (fn [x] (m-result (f x))))) fmap も導出できる m-fmapは m-bind と m-result の組み合わせで定義
  5. 共通点 ── bind ‧ pure ‧ map 名前は違っても、どの⾔語も同じ3つの操作を必ず持つ ⾔語 bind

    pure map Haskell (>>=) return fmap Scala flatMap pure map Clojure m-bind m-result m-fmap ▶ bind と pure 、mapが各⾔語のモナドの共通点
  6. 圏論との対応とモナド則 pure と bind がモナド則を満たすとき、bind‧pure ‧map は圏論的なモナドの構造と対応づけられる 満たすべき3つのモナド則 圏論の構造 →

    プログラミングでの対応 (Haskell) 左単位律 pure で包んで bind ≡ 直接適⽤ ⾃⼰関⼿ T : C → C 対象関数: 型コンストラクタ m // m -> m aに写す pure a >>= f == f a 射関数: fmap :: (a -> b) -> (f a -> f b) 右単位律 pure で包み直しても元のまま ⾃然変換 η : Id ⇒ T 射関数: pure :: a -> m a ⾃然変換 μ : T² ⇒ T 射関数: join :: m (m a) -> m a (join x = x >>= id として bind から定義可能) m >>= pure == m 結合律 bind の結合順序を変えても結果は同じ (m >>= g) >>= h == m >>= (\x -> g x >>= h)
  7. 圏論的イメージ (モナド則) 左単位律 pure で包んで bind ≡ 直接適⽤ pure a

    >>= f == f a 右単位律 pure で包み直しても元のまま m >>= pure == m 結合律 bind の結合順序を変えても結果は同じ (m >>= g) >>= h == m >>= (\x -> g x >>= h)
  8. できること 1/2 値に⽂脈‧計算効果を付与する data Maybe a = Nothing | Just

    a findUser :: Int -> Maybe String findUser 1 = Just "Alice" findUser 2 = Just "Bob" findUser _ = Nothing Just u 値が取れた場合の処理 → main = case (findUser 1) of Just user -> print user Nothing -> print "No User" 同じ仲間: IO Either State List Nothing 失敗した場合の処理を書く 戻り値の型が Maybe aなので、呼び出し側は失敗可能性 という⽂脈を知ることができる ── どれも「⽂脈‧計算効果の付与」で共通
  9. できること 2/2 統⼀的なインターフェースで操作する do 記法 bind チェーン findUser :: Int

    -> Maybe User findCompany :: User -> Maybe Company findAddress :: Company -> Maybe Address findUser :: Int -> Maybe User findCompany :: User -> Maybe Company findAddress :: Company -> Maybe Address do findUser 1 >>= \user -> findCompany user >>= \company -> findAddress company >>= \address -> return address user <- findUser 1 company <- findCompany user address <- findAdress company return address = 同じ意味 <- は各関数が返した⽂脈(Maybe etc…)の中の値を束縛する。前の結果が取れたら次へという合成の流れを⼿続き的に書ける
  10. できること 2/2 do 記法に当たる構⽂は各⾔語にある for 内包表記 (Scala) domonad マクロ (Clojure)

    for { user <- findUser(1) company <- findCompany(user) address <- findAddress(company) } yield address (domonad maybe-m [user (find-user 1) company (find-company user) address (find-address company)] address) 実現⽅法は⾔語ごとに違うが、いずれもbind チェーンを⼿続き的に書くための構⽂という点で揃っている
  11. まとめ 1 bind と pure, map を持ち、モナド則を満たすもの = モナド 2

    失敗‧状態‧IO などの⽂脈‧計算効果を型で表現できる 3 do 記法などの統⼀的な構⽂で⼿続き的に合成できる 使い所 ⽂脈を保ったまま関数合成したい場⾯。特に前の計算結果を使って次の計算を決めたいとき。