はじめに
Youtube をのぞいていたら、下記の動画が表示されたので観てみました。
圏とは?プログラマに人気と噂の圏について話します。 プログラマ向け圏論入門 1 - Youtube
数学的な説明だとよく分からなくなるので、知っているプログラムに変換してもらってから、数学的な説明に戻ると理解が深まるかなと、そういえば圏論とSQLは相性がいいと思っていたので、ChatGPTに「圏論をSQLで説明できますか」とお願いしてみました。
回答
むしろ圏論は「構造と合成」を扱うので、SQLの世界とかなり相性がいいです。無理に難しい用語で説明するより、対応関係で見ると一気に腑に落ちます。
圏論 ↔ SQL の対応イメージ
1. 対象(Object)= テーブル
圏論の「対象」は、SQLではテーブルだと考えられます。
Users(id, name)
Orders(id, user_id, total)
この場合
- Users = 対象
- Orders = 対象
2. 射(Morphisms)= 関係(JOIN可能な対応)
射は「対象から対象への写像」ですが、SQLでは外部キーやJOINの関係に相当します。
Orders.user_id → Users.id
これは Orders → Users という「射」
3. 合成(Composition)= JOINの連鎖
圏論の核心は「射は合成できる」ことです。
SQLだとまさにこれ
SELECT *
FROM Orders o
JOIN Users u ON o.user_id = u.id
JOIN Countries c ON u.country_id = c.id;
これは、Orders → Users → Countries という射の合成です。
圏論では、
(f ∘ g) ∘ h = f ∘ (g ∘ h)
SQLでもJOINの順序を変えても意味は同じ(最適化は別)
4. 恒等射(Identity)= そのまま返すSELECT
圏論では、すべての対象に「何もしない射」があります。
SQLならこれ
SELECT * FROM Users;
Users → Users の恒等射
5. 関手(Functor)= クエリ(構造を保つ変換)
関手は「圏から圏への写像」で、構造(合成)を保ちます。
SQLでは
SELECT id, name FROM Users;
これは
- 元のUsersテーブル(圏A)
- 投影されたテーブル(圏B)
への変換
重要なのは、JOIN構造(関係)を壊さない → 関手っぽい
6. 自然変換(Natural Transformation)= クエリの変換ルール
例えば
SELECT name FROM Users
と
SELECT UPPER(name) FROM Users
これは「関手間の変換」と見なせます。
データの構造は同じで、処理だけ違う。
直感的まとめ
圏論的にSQLを見ると
- テーブル = モノ(対象)
- 外部キー = 矢印(射)
- JOIN = 矢印の合成
- SELECT = 関手的な変換
つまり、SQLは「データの圏」に対する操作言語と見なせる。
- 普通のSQLの理解:テーブルをJOINしてデータを取る
- 圏論的な理解:関係(射)を合成して結果を得る
LINQでの圏論対応
1. SQL → LINQ → 圏論 の対応
まずは同じ処理を並べます。
SELECT u.name
FROM Users u
JOIN Orders o ON u.id = o.user_id
WHERE o.total > 100;
var result =
from u in Users
from o in Orders
where u.Id == o.UserId && o.Total > 100
select u.Name;
2. これを圏論で見ると
このLINQ、実はこう分解できます。
Users
.SelectMany(u => Orders, (u, o) => new { u, o })
.Where(x => x.u.Id == x.o.UserId && x.o.Total > 100)
.Select(x => x.u.Name);
ここからが核心です。
3. モナドとしての構造
Select(写像)= Functor
.Select(x => x.u.Name)
「中身だけ変える」
これは圏論の関手(Functor)
Where(フィルタ)= モナド的フィルタ
.Where(...)
条件に合うものだけ残す
これは数学的には
- Maybe
- List
などのモナドの「絞り込み」
SelectMany(超重要)= bind(モナドの本体)
.SelectMany(...)
これが本質です。
「入れ子構造を平坦化しながら結合する」
SQLのJOINそのもの
平坦化は後述
モナド則(Monad Laws)とは何か
モナドには3つのルールがあります。
return x >>= f = f x
m >>= return = m
(m >>= f) >>= g = m >>= (x => f(x) >>= g)
これをLINQで見る
LINQに置き換えると
-
return → new[] { x }(または単一要素の列) >>= → SelectMany
new[] { x }.SelectMany(f) == f(x)
1個だけのデータに対して処理するのと同じ
xs.SelectMany(x => new[] { x }) == xs
「何もしないJOIN」は元に戻る
(xs.SelectMany(f)).SelectMany(g)
==
xs.SelectMany(x => f(x).SelectMany(g))
JOINの順序を変えても意味が同じ
SQLとの対応(本質)
SQLのこれ
A JOIN B JOIN C
は実は2通りに解釈できます
(A JOIN B) JOIN C
A JOIN (B JOIN C)
結果は同じ(論理的には)
これが、モナドの結合法則
なぜSelectManyがあの形なのか
.SelectMany(x => f(x))
これの意味は、xから新しい文脈(コレクション)を作る
Users.SelectMany(u => Orders)
これは実は、User → List<Order>という関数を使っている。
なぜSelectではダメなのか?
Users.Select(u => Orders)
これだと、List<List<Order>> となり、入れ子になる。
SelectManyの役割
List<List<T>> → List<T>
平坦化(flatten)とは、入れ子になった構造を1段に潰すこと
平坦化の理解
直感的理解として、配列で見ると
var data = new[]
{
new[] { 1, 2 },
new[] { 3, 4 }
};
これは、int[][]で、二重構造(入れ子)
平坦化(flatten)すると、一段になる
[1, 2, 3, 4]
LINQで見ると
data.SelectMany(x => x)
SQLで見ると
SELECT *
FROM Users u
JOIN Orders o ON u.Id = o.UserId;
JOINの結果は、1つのテーブル(平坦)
でも概念的には、UserごとにOrdersのリストがある。
それを潰している。
モナドはこの2つでできています。
map(Select) + flatten
型で見ると
map:
M<T> → (T → U) → M<U>
flatten:
M<M<T>> → M<T>
合わせると
bind(SelectMany):
M<T> → (T → M<U>) → M<U>
ここが圏論の核心
モナドの正体
bind = map + flatten
つまり
- Select = map
- SelectMany = map + flatten
4. モナドとして書くとこうなる
抽象的に書くと
M<T> という構造がある(ListやDB結果)
bind:
M<T> → (T → M<U>) → M<U>
SQL/LINQでは
Users: M<User>
Orders: M<Order>
User → Orders を使って結合
これが JOIN
5. 直感的な対応
| 概念 | SQL | LINQ | 圏論 |
|---|---|---|---|
| データ集合 | テーブル | IEnumerable | 対象 |
| 関係 | 外部キー | ラムダ | 射 |
| JOIN | JOIN | SelectMany | bind |
| SELECT | SELECT | Select | Functor |
| WHERE | WHERE | Where | フィルタ |
6. 一番重要な理解
普通の理解
JOINはテーブルをくっつける
圏論的理解
文脈(コンテナ)の中で関数を合成している
7. Maybeモナドとの対応
Maybeはモナドの中でも一番シンプルで、一言でいうなら 「値があるかもしれないし、ないかもしれない」型 です。
しかも、実務に直結します(NULL・Optionalの正体です)
Maybe = NULLを安全に扱う仕組み
Maybe<T> =
Just T (値あり)
| Nothing (値なし)
NULL
SQLのNULLは、Maybeモナド
User? user = FindUser(id);
user?.Name
これは
Maybe<User> → Maybe<string>
Select(Functor)
JOIN
SQLのJOINには2種類あります
- INNER JOIN
- LEFT JOIN
これ、実は
- INNER JOIN → Listモナド
- LEFT JOIN → Maybeモナド(存在しない場合は
NULL)
8. 圏論的まとめ
SQL/LINQの本質は、モナドの中で関数を合成している
もっとシンプルに言うと
値を直接操作しているのではない
文脈(List / DB / NULL)付きの値を操作している
結論
SQL・LINQ・圏論を一言でまとめると
データではなく“文脈付きデータ”の合成ルール
最後に
ChatGPTだと、どんどん回答を深掘りしていって理解が追いつかず、余計わけが分からなくなるので、一旦これくらいにしておきます。