✍️ 필사 모드: 関数型プログラミングの実用価値 深掘りガイド — Monad, Functor, Pure Function, Haskell, Erlang/Elixir, Gleam, Effect-TS, Phoenix LiveView (2025)
日本語TL;DR — 関数型プログラミング (FP) はもはや「数学者の趣味」ではなく、並行性と分散の時代の必須思考法となった。React Hooks は関数型の状態管理、Redux は
reduce、RxJS は Observable Monad、Swift/Rust/Kotlin のOption/Resultは Maybe/Either Monad、Erlang/Elixir は Pure Function と Actor モデルで 99.9999999% (nine nines) の可用性を実現する。2024-2025 年の実務では、Effect-TS (TypeScript)、Gleam (BEAM 上の型付き言語)、Elixir + Phoenix LiveView、Roc (Elm の後継)、Unison (分散 Pure 言語) といった新世代が台頭した。本稿は「Side Effect 管理」という FP の本質がなぜクラウド時代にいっそう重要になったのか、Monad を圏論なしでどう理解するか、現代言語が FP から何を学んだかを追跡する。
なぜ今、関数型か
FP は 1958 年の LISP に始まり、1980-90 年代に Haskell・ML 系で花開いたが、産業界では長らく「アカデミアの言語」扱いだった。2010 年代半ばから潮目が変わった:
- 2015: React が Immutable props + Pure render function を宣言
- 2016: Redux —
state = reducer(state, action)、実質reduce - 2017: React Hooks 提案 (2019 正式化) — Class ではなく関数
- 2019: Rust 1.0 が Ownership + Result/Option を全面採用
- 2020: Swift 5.5 —
async/await + Task + Result - 2021: Kotlin
Result<T>、JavaOptional<T>が主流化 - 2023: TypeScript Effect-TS 1.0 — "TypeScript の Haskell"
- 2024: Gleam 1.0 — BEAM (Erlang VM) 上の型付き関数型言語
- 2024: Zed Editor、Fly.io Phoenix LiveView — Elixir エコシステム復活
- 2025: WhatsApp (20億ユーザー)、Discord、Pinterest、Bleacher Report が Erlang/Elixir を拡大
なぜ今か、3 つの理由:
- 並行性 — クラウド時代の数万同時リクエストと共有状態の地獄 → 不変性とメッセージパッシングが解
- 信頼性 — 分散システムの Partial Failure → "let it crash" 哲学の再評価
- 型安全性 — 代数的データ型 + Pattern Matching がバグ削減に決定的
Pure Function — すべての出発点
Pure Function: 同じ入力 → 常に同じ出力、Side Effect なし。
// 純粋
function add(a, b) { return a + b }
function double(arr) { return arr.map(x => x * 2) }
// 非純粋
let counter = 0
function incCounter() { counter++; return counter }
function logAndAdd(a, b) {
console.log(a, b)
return a + b
}
function fetchUser(id) {
return fetch(`/api/users/${id}`)
}
Pure Function の実務価値
- テストしやすい — 入力のみで再現可能、Mock 不要
- 並列化可能 — 共有状態がなくスレッドセーフ
- Memoize 可能 — 同じ入力はキャッシュ可能 (React.memo, useMemo)
- 推論可能 — 「グローバル状態の変更はない」と読み手が信頼できる
- 再構成可能 — 合成 (compose) が予測可能
Referential Transparency (参照透過性): 関数呼び出しをその結果値に置き換えてもプログラムの意味が変わらない。Pure Function の別名。
Side Effect はなくならない
現実のプログラムは I/O、DB、ネットワークなしに動かない。FP の核心的洞察は Side Effect をなくすのではなく、境界に集め、型で表現すること。
- Haskell:
IO a型 - Rust:
Result<T, E>型 - Effect-TS:
Effect<R, E, A>— requirement, error, value - Elm: Cmd/Sub — ランタイムに Side Effect を委譲
Immutability — 変更を禁じる設計
// Mutable
const arr = [1, 2, 3]
arr.push(4)
const obj = { a: 1 }
obj.a = 2
// Immutable
const arr2 = [1, 2, 3]
const arr3 = [...arr2, 4]
const obj2 = { a: 1 }
const obj3 = { ...obj2, a: 2 }
なぜ Immutable が重要か
- 変更追跡が容易 — React の
prev === next比較が O(1) - 並行性安全 — 複数スレッドが同時に読んでも問題なし
- タイムトラベルデバッグ — Redux DevTools が可能な理由
- Undo/Redo が簡単 — 過去の状態を保持するだけ
- Memoize が容易 — 同じ参照 = 同じ値
Persistent Data Structure — 不変のコストを解決
素朴な Immutable は変更のたびに全体コピー (O(n))。Persistent Data Structure は構造共有で O(log n) を達成。
- Immutable.js — List, Map, Set
- Immer — draft に書くと内部で不変コピー
- Clojure — Hash Array Mapped Trie を標準内蔵
- Scala — Vector, HashMap が Persistent
import { produce } from 'immer'
const state = { users: [{ id: 1, name: 'Alice' }] }
const nextState = produce(state, draft => {
draft.users.push({ id: 2, name: 'Bob' })
draft.users[0].name = 'Alice Updated'
})
高階関数 — 関数を値として
[1, 2, 3].map(x => x * 2)
[1, 2, 3].filter(x => x > 1)
[1, 2, 3].reduce((acc, x) => acc + x, 0)
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x)
const toString = x => x.toString()
const exclaim = s => s + '!'
const shout = s => s.toUpperCase()
const greet = compose(shout, exclaim, toString)
greet(42) // '42!'
const curry = (fn) => {
return function curried(...args) {
if (args.length >= fn.length) return fn(...args)
return (...more) => curried(...args, ...more)
}
}
const add = curry((a, b, c) => a + b + c)
add(1)(2)(3)
Pipeline — 左から右へ
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x)
const result = pipe(
filter(u => u.active),
map(u => u.email),
xs => xs.join(', ')
)(users)
Elixir は Pipe 演算子が言語内蔵:
users
|> Enum.filter(& &1.active)
|> Enum.map(& &1.email)
|> Enum.join(", ")
Option / Maybe — null のない世界
Tony Hoare は null を "billion-dollar mistake" と呼んだ。FP 言語は Option 型で「値なし」を型で表現する。
fn find_user(id: u64) -> Option<User> {
if id == 0 { None } else { Some(User { id, name: "Alice".into() }) }
}
find_user(42)
.map(|u| u.name)
.unwrap_or("anonymous".into())
import * as O from 'fp-ts/Option'
import { pipe } from 'fp-ts/function'
const findUser = (id: number): O.Option<User> =>
id === 0 ? O.none : O.some({ id, name: 'Alice' })
const result = pipe(
findUser(42),
O.map(u => u.name),
O.getOrElse(() => 'anonymous')
)
findUser :: Int -> Maybe User
findUser 0 = Nothing
findUser id = Just User { userId = id, userName = "Alice" }
Result / Either — 失敗を型で表す
例外は関数シグネチャに現れず「見えない制御フロー」を作る。Result / Either は成功/失敗を型で表現する。
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 { Err("division by zero".into()) }
else { Ok(a / b) }
}
let result = divide(10.0, 2.0)?;
import { Effect } from 'effect'
const divide = (a: number, b: number): Effect.Effect<never, string, number> =>
b === 0
? Effect.fail("division by zero")
: Effect.succeed(a / b)
const program = Effect.gen(function* () {
const x = yield* divide(10, 2)
const y = yield* divide(x, 0)
return x + y
})
Monad — 一体なにか
Monad は FP の悪名高い概念。圏論に由来するが、**実務視点では「連鎖可能な Wrapping Type」**と理解すれば十分。
定義 (実用的)
Monad は次の 2 演算を提供する型:
of(x)— 値xを Monad で包む (unit,pure,return)flatMap(f)— Monad 内の値にfを適用、fは Monad を返す (bind)
type Maybe<T> = { tag: 'some', value: T } | { tag: 'none' }
const of = <T>(x: T): Maybe<T> => ({ tag: 'some', value: x })
const flatMap = <A, B>(m: Maybe<A>, f: (a: A) => Maybe<B>): Maybe<B> =>
m.tag === 'some' ? f(m.value) : m
const parseInt = (s: string): Maybe<number> => {
const n = Number(s)
return isNaN(n) ? { tag: 'none' } : { tag: 'some', value: n }
}
const addTen = (n: number): Maybe<number> => of(n + 10)
const result = flatMap(parseInt("42"), addTen)
なぜ Monad か
Monad は Side Effect を型で表し、順次処理するための道具。様々な「Side Effect」を同じインタフェースで扱える:
- Maybe/Option — 値なし
- Either/Result — 失敗
- Promise/Future/IO — 非同期/I-O
- List — 非決定性
- State — 状態変更
- Reader — 環境依存
- Writer — ログ蓄積
Monad Laws
- Left Identity:
of(x).flatMap(f) === f(x) - Right Identity:
m.flatMap(of) === m - Associativity:
m.flatMap(f).flatMap(g) === m.flatMap(x => f(x).flatMap(g))
do-notation — Monad Chain を簡潔に
Haskell:
main = do
line <- getLine
let n = read line :: Int
print (n * 2)
TypeScript (Effect-TS):
const program = Effect.gen(function* () {
const user = yield* fetchUser(1)
const posts = yield* fetchPosts(user.id)
return { user, posts }
})
JavaScript の async/await も Promise Monad の do-notation である。
Functor, Applicative, Monad — 3 層構造
FP の Type Class 階層:
- Functor —
mapのみ。「コンテナの中の値に関数を適用」 - Applicative — Functor +
of+ap。「複数の値を同時に合成」。Haskell の<$>と<*>は Applicative のコンビネータ。 - Monad — Applicative +
flatMap。「前の結果に依存する連鎖」
Haskell — 「数学者の言語」の教訓
Haskell は 1990 年誕生の Pure, Lazy, Statically-typed な FP 言語。実務ではニッチだが、現代言語への影響は巨大である。
Haskell が生んだイノベーション
- Type Class — ad-hoc polymorphism (Rust trait, Scala implicit, Swift protocol の起源)
- Algebraic Data Type — sum + product type (Rust enum, Swift enum, TS discriminated union)
- Pattern Matching — 現代言語すべてが採用
- Monad — I/O を Pure Function で表現
- Lazy Evaluation — 無限リスト
[1..]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
take 10 fibs -- [0,1,1,2,3,5,8,13,21,34]
class Eq a where
(==) :: a -> a -> Bool
data Shape = Circle Double | Rectangle Double Double | Triangle Double Double Double
area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h
area (Triangle a b c) = sqrt (s * (s-a) * (s-b) * (s-c))
where s = (a + b + c) / 2
Haskell の実務利用
- Facebook Sigma — Haskell によるスパム対策 (1 日 100 億リクエスト)
- GitHub Semantic — コード解析
- Standard Chartered — 金融モデリング
- Target — 価格最適化
- Mercury — バンキング (2020 年〜)
Erlang/Elixir — nine nines の可用性
1986 年 Ericsson が電話交換機用に作った Erlang。2012 年 José Valim の Elixir は Erlang VM (BEAM) 上に Ruby 風文法を載せた言語。
BEAM の 4 つの超能力
- 軽量プロセス — 各プロセス 300-400 バイト、数百万同時実行可能
- Preemptive Scheduler — マルチコアに自動分散、プロセス単位 GC
- Message Passing — 共有メモリなし、メッセージのみで通信 (Actor モデル)
- Hot Code Swap — ランタイムでコード差し替え、無停止アップグレード
"Let It Crash" 哲学
defmodule Worker do
use GenServer
def handle_call(:dangerous_op, _from, state) do
result = dangerous_call()
{:reply, result, state}
end
end
defmodule MyApp.Supervisor do
use Supervisor
def init(_) do
children = [
{Worker, []},
]
Supervisor.init(children, strategy: :one_for_one, max_restarts: 3, max_seconds: 60)
end
end
原則: 防御的コードを書かず、失敗させて Supervisor に再起動させる。これが OTP の核心。
Phoenix + LiveView — React なしのリアルタイム UI
defmodule MyAppWeb.CounterLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, assign(socket, count: 0)}
end
def handle_event("inc", _value, socket) do
{:noreply, assign(socket, count: socket.assigns.count + 1)}
end
def render(assigns) do
~H"""
<div>
<p>Count: <%= @count %></p>
<button phx-click="inc">+1</button>
</div>
"""
end
end
サーバーが WebSocket で DOM diff をプッシュ。React なしで SPA 級の UX。Fly.io、Supabase Realtime、Discord が大規模運用。
実利用事例
- WhatsApp — エンジニア 2 名で 200 万同時接続 (2012)、現在 20 億ユーザー
- Discord — Elixir で 500 万同時接続サーバー
- Pinterest — 通知システム Erlang
- Goldman Sachs、Klarna、Bleacher Report
Gleam — 2024 年 1.0、BEAM 上の型付き言語
Elixir が動的型なのに対し、Gleam (Louis Pilfold, 2016-) は 静的型 + BEAM。2024 年 3 月に 1.0 リリース。
import gleam/io
pub type Shape {
Circle(radius: Float)
Square(side: Float)
}
pub fn area(shape: Shape) -> Float {
case shape {
Circle(r) -> 3.14159 *. r *. r
Square(s) -> s *. s
}
}
pub fn main() {
let c = Circle(radius: 5.0)
io.debug(area(c))
}
- Rust 水準の型推論
- Elixir/Erlang モジュールと双方向互換
- JavaScript へのコンパイルも可能
- 文法は Rust 風
Clojure, F#, Scala, OCaml — ハイブリッド FP
Clojure
- JVM 上の LISP 方言 (Rich Hickey, 2007)
- Immutable デフォルト、Persistent Data Structure
- マクロで言語拡張、core.async で CSP
- 利用先: Walmart、Nubank、NASA
(defn add [a b] (+ a b))
(->> [1 2 3 4 5]
(filter odd?)
(map #(* % %))
(reduce +))
F#
- Microsoft (Don Syme, 2005)、OCaml ベース + .NET
- Jet.com、Walmart Labs、金融業界
Scala
- JVM 上のハイブリッド OOP + FP (Martin Odersky, 2003)
- Twitter、LinkedIn、Spotify バックエンド
- Play、Akka、Apache Spark の言語
OCaml
- フランス INRIA、1996
- Jane Street (金融)、Facebook (Flow, Hack)、Docker (初期)、Coq
React, Redux, RxJS — JavaScript の FP 継承
React の核心: UI = f(state)。
function Greeting({ name }) {
return <h1>Hello, {name}</h1>
}
function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
Redux はまさに reduce:
function reducer(state, action) {
switch (action.type) {
case 'INC': return { ...state, count: state.count + 1 }
case 'DEC': return { ...state, count: state.count - 1 }
default: return state
}
}
RxJS は Observable Monad — 時間に沿った値のストリーム:
const click$ = fromEvent(button, 'click').pipe(
map(e => e.clientX),
filter(x => x > 100),
debounceTime(300)
)
Effect-TS — TypeScript の Haskell
2024 年 1.0 リリース。Scala ZIO の TypeScript 版。
import { Effect, pipe, Layer } from 'effect'
interface Logger {
log: (msg: string) => Effect.Effect<never, never, void>
}
const Logger = Context.Tag<Logger>()
const program = Effect.gen(function* () {
const logger = yield* Logger
yield* logger.log("starting...")
const user = yield* fetchUser(1)
yield* logger.log(`got user ${user.name}`)
return user
})
const LoggerLive = Layer.succeed(Logger, {
log: (msg) => Effect.sync(() => console.log(msg))
})
Effect.runPromise(pipe(program, Effect.provide(LoggerLive)))
- DI + Error Handling + Async + Retry + Fiber を型安全に統合
- 大規模 TypeScript アプリで Monad Transformer 地獄を回避
- 学習曲線は急だが、複雑なシステムで生産性が跳ね上がる
並行性モデル
- Actor (Erlang/Elixir/Akka) — 独立プロセス + メッセージボックス、共有メモリなし、数百万並行
- CSP (Go, Clojure core.async) — チャネル経由通信、"share memory by communicating"
- STM — メモリを DB トランザクションのように扱う (Haskell
STM, Clojureref) - Promise/Future (JS, Rust, Python, Swift) — 未来の約束 + async/await
2024-2025 注目の関数型言語
- Roc (Richard Feldman, Elm 後継) — 2024 年 0.1 公開、LLVM ベース、Platform 概念
- Unison — 分散 Pure プログラミング、関数 = ハッシュ
- Koka (Daan Leijen, Microsoft) — Algebraic Effects、Monad の代替
- Lean 4 — 数学証明 + 一般プログラミング (Terence Tao が数学証明に活用)
10 のよくあるアンチパターン
- Monad 地獄 — すべてを Monad で包み可読性を破壊。境界でのみ使う。
- Pure 純粋主義 — I/O を全回避して非実用的。Side Effect は境界に集める。
reduce乱用 — 複雑な Reducer は可読性を破壊。複数の map/filter に分ける。- Lazy Evaluation の乱用 — デバッグ困難、メモリリーク。
- Curry 強制 — JS/TS で全関数 Curry すると IDE の型ヒントが壊れる。
- Immutable 強迫 — ローカル変数は Mutable で良い。境界のみ Immutable。
- 高階関数チェーンの過度な長さ — 10 段の
.map.filter.reduceは命令型の方が読みやすい。 - Monad Transformer Stack — 4-5 層になったら Effect-TS/Koka を検討。
- Actor モデルの誤用 — すべてを Actor にするのはオーバーエンジニアリング。CPU 集約型はデータ指向へ。
- 動的 FP の錯覚 — 型なしの FP は Monad Chain が地獄。型付き言語から始めること。
実践学習チェックリスト (2025)
- map/filter/reduce のチェーンを自在に (どの言語でも)
- Option/Result 型を使う — Rust、Swift、Kotlin から
- Pattern Matching + ADT — TS discriminated union、Rust enum
- 不変データ構造の選定 — Immer、Immutable.js、Clojure、Scala
- Promise Chain ≒ Monad の理解
- RxJS の基本オペレータ — map、filter、mergeMap、switchMap
- React Hooks の関数型本質 — useMemo、useCallback = Memoization
- Actor または CSP パターン — Elixir GenServer または Go channel
- Effect-TS または fp-ts — 複雑な TS アプリで実験
- Haskell を 2 週間探検 — 思考法を拡張
- Elixir + Phoenix LiveView — リアルタイム UI 体験
- Monad Laws の理解 (暗記ではなく直感)
次回予告 — 「Kubernetes はなぜこれほど複雑なのか」
関数型プログラミングが「個別コードの信頼性」を高めるなら、Kubernetes と Cloud Native は「システム全体の信頼性」を扱う。2014 年に Google が内部 Borg 経験をオープンソース化して以来、K8s は事実上クラウドの OS になった。ただしその複雑度は悪名高い。
次回は、K8s が解決した問題と新たに作った問題; Pod/Service/Deployment/Ingress の基本; Control Plane アーキテクチャ (etcd、API Server、Scheduler、Controller); Networking (CNI、kube-proxy、Service Mesh — Istio、Linkerd、Cilium); Sidecar vs Ambient Mesh の論争; GitOps (ArgoCD、Flux); Helm vs Kustomize vs Jsonnet; Platform Engineering (Backstage、IDP); Operator パターンと CRD; マルチクラスタ戦略 (Karmada、Cluster API、KubeFed); コスト最適化 (OpenCost、Kubecost、Karpenter); 2025 K8s トレンド (WASM、eBPF、Gateway API) を扱う。「Kubernetes は本当に必要か」という本質的な問いから実戦運用レシピまで、クラウドネイティブ世界の全体地形を整理する。
현재 단락 (1/339)
FP は 1958 年の **LISP** に始まり、1980-90 年代に Haskell・ML 系で花開いたが、産業界では長らく「アカデミアの言語」扱いだった。2010 年代半ばから潮目が変わった...