Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
関数型プログラミングのメリットって何だろう?
Search
わんこ(Wanko_IT)
July 07, 2026
Programming
210
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
関数型プログラミングのメリットって何だろう?
わんこ(Wanko_IT)
July 07, 2026
More Decks by わんこ(Wanko_IT)
See All by わんこ(Wanko_IT)
バックエンドにElysiaJSを採用して気付いた、良い点・悪い点
wanko_it
1
390
安易なコード批判はやめよう
wanko_it
0
79
『リコリス・リコイル』に学ぶ!! 〜キャリア戦略における計画的偶発性理論と変わる勇気の重要性〜
wanko_it
2
1.2k
V8のJITコンパイルを完全に理解するLT
wanko_it
0
47
Other Decks in Programming
See All in Programming
AWS CDK を「作」ってみた 〜フルスクラッチで見えた CDK の裏側〜 / aws-cdk-from-scratch
gotok365
3
2.8k
霧の中の代数的エフェクト
funnyycat
1
460
ルールを書いて終わらせないハーネスエンジニアリング
yug1224
4
1.8k
5分で問診!Composer セキュリティ健康診断
codmoninc
0
860
仕様駆動開発へのトライを機に チームに適合する手法を模索し続けている話
freee
PRO
0
410
【やさしく解説 設計編・中級 #4】ルールの寿命と、システムの年輪
panda728
PRO
2
180
Go言語とトイモデルで学ぶTransformerの気持ち / fukuokago23-transformer
monochromegane
0
160
freee が目指す データ マネジメント戦略 AI-Ready 時代を支える 攻めのガバナンスとは
freee
PRO
0
260
自動化したのに回らないテスト運用の壁ーAI時代の品質責任と生産性
mfunaki
0
120
ここ半年くらいでAIに作らせたR用ツール
eitsupi
0
370
使用 Meilisearch 建立新聞搜尋工具
johnroyer
0
220
数百円から始めるRuby電子工作
tarosay
0
130
Featured
See All Featured
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
For a Future-Friendly Web
brad_frost
183
10k
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.2k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.8k
Automating Front-end Workflow
addyosmani
1370
210k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
480
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
330
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
430
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Git: the NoSQL Database
bkeepers
PRO
432
67k
Unsuck your backbone
ammeep
672
58k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
Transcript
関数型プログラミングの メリットってなんだろう? 清水日向 社内発表 / 10分
背景 私たちのチームでは関数型言語 Clojure を使って開発しています。日々の開発の中で「関数型プ ログラミングって、結局何が嬉しいんだろう?」と疑問に思い、調べた内容を共有します。 「なんとなく良さそう」で終わらせず、関数型プログラミングがどんなバグや問題を防いでくれ るのかを、実際のコードで確認していきます。難しい理論の話はしないので、気楽に聞いてくだ さい。 ※ コード例は
Clojure ではなく Java / Scala で書いています。括弧だらけの Clojure より読み慣れた構文のほうが、違いが伝 わりやすいので。
アジェンダ 01 関数型プログラミングとは何か 定義を確認し、本質が「不変性」にあることを押さえる 02 値の不変性とは何か 変数で表現する言語と、定数だけで表現する言語のコードを比べる 03 値が不変だと何が嬉しいのか 可変であることの問題2つ
— 共有ミュータブル状態・時間的結合 — をコードで見る 04 まとめ — なぜ合理的な選択なのか 「未知の未知」というソフトウェアの複雑性と不変性の関係
01 ─ 関数型プログラミングとは何か 関数型プログラミングの定義 そもそも、関数型プログラミングとは何でしょうか? 調べると、以下のような特徴が挙げられています。 参照透過性 同じ入力には常に同じ出力を 返す 値の不変性
一度作られた値は決して変更 されない 第一級関数 関数を値として扱い、引数や 戻り値にできる 高階関数の利用 関数を受け取る・返す関数を 活用するスタイル …ただし、広く認められた共通の定義は確立されていない。そこで今回は、ある定義を採用します。
01 ─ 関数型プログラミングとは何か 関数型プログラミングとは、変数のないプログラミングと言え るだろう。関数型プログラムの値は変化しないのである。 『関数型デザイン』Robert C. Martin 今回はこの定義を採用します。つまり、関数型プログラミングの本質は「不変性」に ある。
02 ─ 値の不変性とは何か 値の不変性 Java・C・PHP・Ruby などのオブジェクト指向・手続き型の言語は変数(変更可能な値)で状態を表現する。一方で Clojure・Scala・Erlang などの関数型志向の言語は、定数(不変な値)を基本とする。 非関数型(Java)— 変数を書き換える
class Wallet { private int money; public Wallet(int money) { this.money = money; } public int getMoney() { return money; } public void addMoney(int amount) { money = money + amount; // 中身を書き換える } } Wallet wallet = new Wallet(1000); wallet.getMoney(); // 1000 wallet.addMoney(500); wallet.getMoney(); // 1500 ← 状態が変わった 関数型(Scala)— 新しい値を作る case class Wallet(money: Int) def addMoney(wallet: Wallet, amount: Int): Wallet = Wallet(wallet.money + amount) // 新しい財布を作る val wallet1 = Wallet(1000) val wallet2 = addMoney(wallet1, 500) wallet1.money // 1000 ← 変わっていない! wallet2.money // 1500 ← 新しくできた財布 関数型では、値は定数が基本。状態の変化は「既存の値をもとに作られた新しい定数」として表現する。
値が不変だと、何が嬉しい? 逆から考える — 値が可変であることに伴う問題は2つある 問題 ① ── いまから見る 共有ミュータブル状態 同じインスタンスへの変更が、予期せぬ場所へ波及する
問題 ② 時間的結合 変更の途中に「不正な中間状態」が存在してしまう
03 ─ 値が不変だと何が嬉しいのか 問題① 共有ミュータブル状態(Java) Q. このコード、何が出力されるでしょう? 月末ごとの残高を記録するクラス class BalanceHistory
{ // 特定地点での財布の状態を保持 private List<Wallet> snapshots = new ArrayList<>(); // 「今この瞬間の状態」を記録 public void recordSnapshot(Wallet wallet) { snapshots.add(wallet); } // 記録された履歴を取得 public List<Wallet> getSnapshots() { return snapshots; } } 利用側のコード Wallet wallet = new Wallet(1000); BalanceHistory history = new BalanceHistory(); history.recordSnapshot(wallet); // 1月末: 1000円を記録 wallet.addMoney(500); history.recordSnapshot(wallet); // 2月末: 1500円を記録 wallet.addMoney(-1500); history.recordSnapshot(wallet); // 3月末: 0円を記録 for (Wallet snap : history.getSnapshots()) { System.out.println(snap.getMoney()); }
03 ─ 値が不変だと何が嬉しいのか 予想と異なる出力 直感的な予想 1000 1500 0 各月末に記録した瞬間の残高が、そのまま残っているは ず…
実際の出力 0 0 0 1月末の「1000円」も2月末の「1500円」も消えてしまっ た
03 ─ 値が不変だと何が嬉しいのか 原因: 3要素は同一インスタンス recordSnapshot(Wallet wallet) の引数は参照渡し。リストに追加されるのはコピーではなく、同じWalletへの 参照。3要素が指す実体は1つで、その値は最後の変更(3月末=0円)の状態になっている。 ①
リストの中身を図にすると snapshots[0] ──▶ snapshots[1] ──▶ snapshots[2] ──▶ Wallet(money = 0) たった1つの同じインスタンス ② コードで要素の等価性を確認しても、同一 snapshots.get(0) == snapshots.get(1) // true snapshots.get(0) == snapshots.get(2) // true snapshots.get(1) == snapshots.get(2) // true 共有ミュータブル状態では、処理の影響が予期せぬ形で波及する。アプリ全体を正常に保つために「考えるべき こと」が増大してしまう。
03 ─ 値が不変だと何が嬉しいのか 不変な値による解決(Scala) case class BalanceHistory(snapshots: List[Wallet] = Nil)
{ def recordSnapshot(wallet: Wallet): BalanceHistory = BalanceHistory(snapshots :+ wallet) } // 1月末: 1000円 val wallet1 = Wallet(1000) val history1 = BalanceHistory().recordSnapshot(wallet1) // 2月末: 1500円 val wallet2 = wallet1.addMoney(500) val history2 = history1.recordSnapshot(wallet2) // 3月末: 0円 val wallet3 = wallet2.addMoney(-1500) val history3 = history2.recordSnapshot(wallet3) history3.snapshots.foreach(w => println(w.money)) // 1000, 1500, 0 ← 期待どおり! ポイント 1 プログラム中のすべての値は変更不能かつ 異なる実体であると、言語が保証する ポイント 2 異なる状態のWalletは別々の実体として保 存され、後から変更される心配がない ポイント 3 「スナップショット」が言葉どおりの意味 で機能し、記録した瞬間の状態がそのまま 残る 出力: 1000 → 1500 → 0 ✓
値が可変であることの問題、もう1つ 今度は「空間」ではなく「時間」に潜む問題 問題 ② ── いまから見る 時間的結合 変更の途中に「不正な中間状態」が存在してしまう 問題 ①
── 解決 ✓ 共有ミュータブル状態 不変な値なら、記録した状態が後から変わることはない
03 ─ 値が不変だと何が嬉しいのか 問題② 時間的結合(Java) class Wallet { private int
balance = 1000000; // 残高(100万円) private int count = 0; // 送金回数 // 送金を行うメソッド public void transfer(int amount) { balance -= amount; // ← この瞬間「残高だけ減って回数は0のまま」 // という業務上ありえない財布が存在する count += 1; } } balance と count はセットで変わるべき値だが、 変数は1つずつしか書き換えられないため、途中に 不正な中間状態が実在する瞬間が生まれる。 その結果「この瞬間に参照・操作してはいけない」 という時間軸上の制約が生まれる。コードのどこ にも書かれていないこの依存が時間的結合。 この一瞬を別スレッドが読み取れば再現困難なバグに。 あいだで例外が起きれば不整合なまま残り続ける。 transfer() 実行中の時間軸 balance -= amount; ▶ 不正な中間状態 残高: 99万円 / 送金回数: 0回 ▶ count += 1;
03 ─ 値が不変だと何が嬉しいのか 同時に確定する状態(Scala) case class Wallet(balance: Int, count: Int)
{ def transfer(amount: Int): Wallet = Wallet(balance - amount, count + 1) // ↑ 残高と回数が同時に確定した新しい財布 } val w0 = Wallet(1000000, 0) val w1 = w0.transfer(10000) // w0: (1000000, 0) … 送金前の財布 // w1: ( 990000, 1) … 送金後の財布 // 「残高だけ減った財布」は書きようがない 不変な値では、状態の変化を全フィールドが同時 に確定した新しい値としてしか表現できない。 つまり不正な中間状態が、そもそも実在しない。 存在するのは送金前と送金後、2つの正しい財布 だけ。 中間状態が消えれば「この瞬間に参照・操作して はいけない」という制約も丸ごと消える — 守るべ きルールではなく、ルールが必要な状況自体をな くす。 可変(Java) 1つの財布が時間とともに姿を変え、途中の不正な姿 が存在してしまう。 不変(Scala) 存在するのは w0 と w1 という2つの完成品だけ。途 中がない。
04 ─ まとめ: なぜこれが重要か “Of the three manifestations of complexity,
unknown unknowns are the worst.” John Ousterhout『A Philosophy of Software Design, 2nd Edition』 ソフトウェアの複雑性のうち最悪なのが「未知の未知」— 問題が潜んでいるかどうかさえ、知る術がないこ と。バグが出たとき、初めてその存在に気づく。 ここまで見てきた可変な値に起因するバグは、特定の手順・条件でしか起きない。そのため動作検証やテス トをすり抜け、バグが顕在化するまで問題の存在に気づけない — 未知の未知そのものである。 不変な値なら、不整合な状態はそもそも作れない。「未知の未知」になる前に、言語が芽を摘んでくれる。
04 ─ まとめ まとめ 関数型プログラミングとは、不正な状態を発生させにくくするプログラミングスタイルで ある。 1 本質は不変性 — あらゆる値を定数として扱う
状態の変化は「既存の値をもとにした新しい定数」として表現する 2 共有ミュータブル状態と時間的結合を構造的に排除できる 予期せぬ波及も、不正な中間状態も、そもそも表現できない 3 銀の弾丸ではないが、合理的な選択肢 再現困難でテストをすり抜ける「未知の未知」型バグの発生確率を下げられる ご清聴ありがとうございました