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

10分で学ぶ すてきなモナド

Avatar for soukouki soukouki
October 18, 2025

10分で学ぶ すてきなモナド

Zli 大LT 2025 秋 in Aizu(2025-10-18)で発表したスライドです。

モナドとはなにか?という難しい概念について、計算効果という点から説明をした発表です。

圏論についてはあまり触れていません。

Avatar for soukouki

soukouki

October 18, 2025
Tweet

More Decks by soukouki

Other Decks in Education

Transcript

  1. 連絡先 ActivityPub/Misskey: @[email protected] ログイン頻度 : ほぼ毎日 Twitter: @sou7_  _  _

    ログイン頻度 : 4ヶ月に1回 GitHub: @soukouki Discord: @sou_7 ブログ: https://ob.sou7.io -> 3
  2. null → Option型 Option型って何? 「値があるかもしれないし、な いかもしれない」ことを型で表 現したもの nullを使うと、実行時にnull参 照のエラーが起きてしまった 型で表現することで、コンパイ

    ル時にミスを検出できる 言語の変遷 nullを直接使う時代遅れの言語 C言語 (1972年) Java (1995年) Go (2009年) ← ? Option型を使う素晴らしい言語 Haskell (1990年) Rust (2010年) Kotlin (2011年) 8
  3. 記述が煩雑になる問題 Option型やResult型を使うことでエ ラーをコンパイル時に検出できるよ うになりました。 しかし、それらの型から値を取り出 す際にパターンマッチを使う必要が あり、ネストが深く読みづらいコー ドになってしまう問題がありまし た。 def

    readConfig(): Option[String] = Some("42") def process(): Option[Int] = { readConfig() match { case Some(s) => s.toIntOption match { // ここからさらに処理を追加すると // さらにパターンマッチのネストが深くなる case Some(n) => Some(n) case None => None } case None => None } } 10
  4. とりあえずの解決策 : 構文の追加 この問題を解決するために、最近の言語ではいろんな構文が追加されていま す。 Rustの ? 演算子 fn read_config()

    -> Option<String> { Some("42".to_string()) } fn parse_int(s: &str) -> Option<i32> { s.parse::<i32>().ok() } fn process() -> Option<i32> { let s = read_config()?; // None ならここでreturn let n = parse_int(&s)?; // None ならここでreturn // ここにさらに処理を追加しても煩雑にならない Some(n) } Kotlinの ?: 演算子 fun readConfig(): String? { return "42" } fun parseIntOrNull(s: String): Int? { return s.toIntOrNull() } fun process(): Int? { val s = readConfig() ?: return null val n = parseIntOrNull(s) ?: return null // ここにさらに処理を追加しても煩雑にならない return n } 11
  5. 非同期処理に付随するasync /await async / await 登場以前の Promise地獄 fetch(url).then(response => {

    response.json().then(data => { fetch(anotherUrl).then(r => { r.json().then(anotherData => { // さらに処理 }); }); }); }); async / await でスッキリ async function fetchData() { const response = await fetch(url); const data = await response.json(); const r = await fetch(anotherUrl); const anotherData = await r.json(); // さらに処理 } ( anotherUrl は data 内にURLが含まれていると仮定しています) 13
  6. モナドとdo構文の例 Optionモナドとdo構文 process :: Maybe Int process = do s

    <- readConfig n <- parseInt s return n (readConfigとparseIntの定義) readConfig :: Maybe String readConfig = Just "42" parseInt :: String -> Maybe Int parseInt s = case reads s of [(n, "")] -> Just n _ -> Nothing これはOption型の例ですが、Result型やPromise型、IOモナドなどでも同様に do構文を使えます。 16
  7. 計算作用とモナド 関数に付随する演算子が必要なのは、その関数に計算作用が存在するからで す。 計算作用の例 値を返さないことがある (Option型) エラーが発生することがある (Result型) 非同期に実行される (Promise型)

    副作用がある (IOモナド) これらの計算作用の畳み込みを自動で行ってくれるのがモナドです。 (畳み込みは結合法則を満たし、単位元もあります。そう、モナドは(モノイダル圏における)モノイドなのです。 ) 17
  8. モナドが果たす役割 ( は自己関手で表した計算効果) 関数 f 型 A 型 T(B) 関数

    g 型 B 型 T(C) 関数 と関数 は計算効果 を含んだ型を持っています。そのままでは関数 の 出力を関数 に渡すことができません。 関数 f 型 A 型 T(B) 関数 T(g) 型 T(T(C)) モナドのμ 型 T(C) ここで、関数 を関手 で持ち上げて、関数 の出力を関数 に渡します。 そして、その出力である を、自然変換 を使って に纏めます。 つまり、モナドを使うことで計算効果の畳み込みができるようになります。 18
  9. モナドの定義 簡単なモナドの定義 圏 におけるモナドとは、 自己関手 自然変換 自然変換 からなり、等式 結合律 :

    単位律 : が成り立つものである。 難しいモナドの定義 モナドは単なる自己関手の圏におけ るモノイド対象だよ。何か問題で も? 21
  10. 参考文献 圏論勉強会 第13回 @ ワークスアプリケーションズ 圏論について触れる資料の最後でモナドについても触れています。圏論に ついての話を知らなくても、図によってモナドのイメージを掴むことがで きます。 The Haskell

    Programmer’s Guide to the IO Monad — Don’t Panic 簡単な方のモナドの定義をきちんと理解するための資料です。 Notions of computation and monads - Eugenio Moggi 計算作用にモナドを利用することを提案した1989年の論文です。 22
  11. 線型代数対話 第1巻圏論的集合論 集合圏とトポス - 西郷甲矢人・能美十三 共著 モナドのためなら半分くらい読めばOKです。 線型代数対話 第2巻モノイドの線型代数―モノイダル構造から行列計算へ ―

    - 西郷甲矢人・能美十三 共著 モナドが登場します。理解するには第1巻を読む必要があります。 この本のモナドの部分まで勉強すると「モナドは単なる自己関手の圏にお けるモノイド対象である」の意味が分かるようになります。 ベーシック圏論 - 斎藤 恭司 監修、土岡 俊介 訳 圏論の入門書として真っ先に挙げられる本です。モナドは載ってなさそう です。 23