圏論は抽象的な数学の分野で、業務のソフトウェアとは縁遠く見えます。けれども、圏論を道具としてソフトウェアに使う研究は進んでいて、成果がライブラリやツールとして公開されています。この記事ではそうした研究をしている研究所の一つ Topos Institute の公開物を使って、圏論が業務システムに近い場面で何に使えるのかを、具体的な例で見ます。
答えの要点は次のとおりです。データベースのスキーマは「圏」として書けて、外部キーを続けて辿る操作は、圏の矢印(射)をつなぐ「合成」として扱えます。新旧のスキーマの対応を「関手」として書くと、新しいデータを古いスキーマで読む移し方と、古いデータを新しいスキーマへ移す移し方が、その対応から決まります。書いた対応が関手になっているかは、プログラムで確かめられます。小さなモデルを部品として、つなぎ方を図で書いて大きなモデルを組み立てることもできます。どれも圏論が何でも解くという話ではなく、対応やつなぎ方を曖昧さなく書くための道具として使う例です。
例は Julia のライブラリ Catlab.jl で書きました。Catlab.jl は、Topos Institute がかかわるライブラリ群 AlgebraicJulia の中心になるライブラリです。コードは2026年11月12日に、Ubuntu 24.04 のコンテナに入れた Julia 1.11.7 と Catlab 0.17.6 で実際に動かしました。ブラウザのツール CatColab は公式の資料で紹介するだけで、操作して試してはいません。
Topos Institute とその公開物
Topos Institute は米国カリフォルニア州の非営利団体として登録された研究所です。英国にも法的には独立した慈善団体 Topos Research UK があり、2つで同じ使命を掲げています1。サイトでは自らを「使命にもとづく非営利の研究所」と紹介し、違いを越えた協力を可能にする新しい技術を通じて、研究し、作り、人の役に立つと述べています2。チームのページによると、CEO は Brendan Fong、Senior Scientist は David Spivak です3。
活動は4つの柱に分かれています4。この記事に関係するのは、そのうち2つです。
- 基礎研究: 数学の圏論にもとづいて、複雑なシステムを理解するための理論を作ります。
- 応用研究(translational research): その数学をソフトウェアのライブラリやツールに移し、実際に使える形にします。
flowchart TB
accTitle: Topos Institute の研究と公開物
accDescr: Topos Institute の基礎研究が論文と書籍として公開され、応用研究がライブラリの Catlab.jl などの AlgebraicJulia とブラウザのツールの CatColab として公開されることを示す。
T["Topos Institute"] --> R["基礎研究"]
T --> A["応用研究"]
R --> P["論文・書籍"]
A --> L["AlgebraicJulia(Catlab.jl など)"]
A --> C["CatColab(ブラウザのツール)"]
図1: 基礎研究は論文と書籍に、応用研究はライブラリとツールになって公開される。
公開物のうち、業務の開発者が触れやすいものを挙げます。
- AlgebraicJulia: 応用圏論(圏論を具体的な分野の問題に当てはめる研究)にもとづく科学技術計算を目指すライブラリ群です5。Topos Institute のブログは、自分たちや仲間の数学の考えをソフトウェアにすることを主な仕事の一つに挙げ、これまでは主に AlgebraicJulia がそれに当たると書いています6。その中心になるパッケージ Catlab.jl は「Julia 言語で応用圏論を扱うための枠組み」で、MIT ライセンスで公開されています7。ほかに、後で触れる StockFlow などのパッケージがあります。この記事の例はすべて Catlab.jl で書きました。
- CatColab: Topos Institute とその協力者が作っている、ブラウザでモデルを作り、共同で編集するためのツールです。公式のヘルプは「形式的で、相互運用できる、概念的なモデリングのための共同作業環境」と説明しています8。AlgebraicJulia のライブラリは圏論の背景知識がかなりないと使えず、使える人が限られていました。CatColab はこの反省から、近づきやすいツールを Topos Institute 自身が作ることにして始まりました。2024年の発表では、圏論の処理を Rust で書き、ブラウザの中で動かしていると説明しています6。モデルは「ロジック」と呼ぶ種類ごとに作り、ロジックには Olog(ontology log、概念とその関係を書く図)、圏論的なデータベースのスキーマ、ペトリネット、因果ループ図、ストック・フロー図などがあります9。ソースは MIT と Apache-2.0 のデュアルライセンスで公開されています10。開発中のツールで、重要なデータや機密のデータは保存しないよう注意書きがあります8。
- 書籍と論文: Brendan Fong と David Spivak の『Seven Sketches in Compositionality』は、データベースや電気回路などの応用ごとに圏論の考え方を1つずつ紹介する入門書で、arXiv で読めます11。Nelson Niu と David Spivak の多項式関手の本も arXiv で読めます12。
flowchart TB
accTitle: AlgebraicJulia と CatColab の関係
accDescr: AlgebraicJulia は中心の Catlab.jl と StockFlow などのパッケージからなる Julia のライブラリ群で、その学びにくさへの反省からブラウザのツール CatColab が別に作られ、CatColab ではロジックごとにモデルを作ることを示す。
AJ["AlgebraicJulia(Julia のライブラリ群)"] --> CL["Catlab.jl(中心のパッケージ)"]
AJ --> SF["StockFlow など"]
AJ -.->|"学びにくさへの反省"| CC["CatColab(ブラウザのツール)"]
CC --> LG["ロジック:Olog、スキーマなど"]
図2: CatColab は、AlgebraicJulia の学びにくさへの反省から、ブラウザのツールとして別に作られた。
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全7件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
圏論の言葉を業務の言葉に置き換える
この記事で使う圏論の言葉は、圏、対象、射、関手の4つです。圏(けん)は「対象」と、対象から対象への矢印である「射(しゃ)」の集まりです。A から B への射と B から C への射があれば、2つをつないで A から C への射を作れます。これが合成です。どの対象にも何もしない射(恒等射)が1つずつあり、3つ以上の射をつなぐときは、どこから先につないでも結果は同じです(結合律)。関手(かんしゅ)はある圏の対象と射を、つながり方を保ったまま別の圏の対象と射に対応させるものです。
データベースに当てはめると、表が対象、外部キーが射になります。受注の表から顧客の表への外部キーは「受注 → 顧客」という射です。列も表から値の型(名前や金額など)への射として扱います。スキーマに書くのは表、外部キー、列だけですが、圏の射はそれらを続けて辿る経路すべてで、外部キーを2回辿る操作は射の合成に当たります。David Spivak は2010年の論文で、スキーマを圏として、データをスキーマの圏から集合への関手として扱う考え方を示しました13。表ごとに行の集合を、外部キーごとに行から行への対応を割り当てたものがデータです。Catlab.jl ではこのデータを C-set と呼び、列の値を持たせたものを acset(attributed C-set)と呼びます14。
flowchart TB
accTitle: 圏論の言葉と業務の言葉の対応
accDescr: 圏はスキーマに、対象は表と値の型に、射は外部キーと列に、射の合成は外部キーや列を続けて辿る経路に当たり、関手はスキーマどうしの対応に当たることを示す。
S["圏:スキーマ"] --> O["対象:表・値の型"]
S --> M["射:外部キー・列"]
M --> K["射の合成:続けて辿る経路"]
U["関手:スキーマどうしの対応"] -.-> S
図3: 圏はスキーマ、対象は表と値の型、射は外部キーと列、関手はスキーマどうしの対応に当たる。
受注・顧客・商品のスキーマを Catlab.jl で書く
最初の例は受注・顧客・商品の3つの表を持つ小さなスキーマです。受注は顧客と商品への外部キーを持ち、顧客と商品には名前、商品には単価、受注には数量の列があります。Catlab.jl では @present でスキーマを書き、Ob が表、Hom が外部キー、AttrType が列の型、Attr が列になります。
flowchart TB
accTitle: 受注・顧客・商品のスキーマ
accDescr: 受注の表が外部キーで顧客の表と商品の表を指し、顧客は名前、商品は名前と単価、受注は数量の列を持つことを示す。
O["受注(数量)"] -->|"customer"| C["顧客(名前)"]
O -->|"product"| P["商品(名前・単価)"]
図4: 受注は顧客と商品を外部キーで指す。
using Catlab
# 受注・顧客・商品のスキーマ。Ob が表、Hom が外部キー、Attr が列
@present SchOrders(FreeSchema) begin
(Customer, Product, Order)::Ob
customer::Hom(Order, Customer)
product::Hom(Order, Product)
(Name, Count, Yen)::AttrType
customer_name::Attr(Customer, Name)
product_name::Attr(Product, Name)
price::Attr(Product, Yen)
quantity::Attr(Order, Count)
end
@acset_type Orders(SchOrders)
@acset_type はこのスキーマから、データを入れる型を作ります。Orders{String, Int, Int} の3つの型は列の型 Name、Count、Yen に順に当てる Julia の型です。@acset でデータを入れ、外部キーを辿って受注ごとの金額を出し、逆向きに「A社の受注」を探しました。
db = @acset Orders{String, Int, Int} begin
Customer = 2
customer_name = ["A社", "B社"]
Product = 2
product_name = ["ねじ", "ナット"]
price = [12, 8]
Order = 3
customer = [1, 2, 1]
product = [1, 1, 2]
quantity = [100, 50, 200]
end
# 外部キーを辿る: 受注 → 顧客の名前、受注 → 商品の単価
for o in parts(db, :Order)
name = db[o, (:customer, :customer_name)]
item = db[o, (:product, :product_name)]
total = db[o, :quantity] * db[o, (:product, :price)]
println("受注$o: $name が $item を $(db[o, :quantity]) 個、$total 円")
end
# 逆向き: A社(顧客1)の受注を探す
println("A社の受注: ", incident(db, 1, :customer))
実行した結果です。
受注1: A社 が ねじ を 100 個、1200 円
受注2: B社 が ねじ を 50 個、600 円
受注3: A社 が ナット を 200 個、1600 円
A社の受注: [1, 3]
db[o, (:customer, :customer_name)] の (:customer, :customer_name) は、外部キー customer と列 customer_name を続けて辿る経路で、圏論でいう射の合成に当たります。SQL なら結合(JOIN)を書く場面です。incident は外部キーを逆に辿り、顧客1を指している受注を返します。
flowchart TB
accTitle: 外部キーを続けて辿る経路
accDescr: 受注から外部キーの customer で顧客へ、顧客から列の customer_name で名前へ進む2つの矢印をつなぐと、受注から名前への1つの経路になることを示す。
O["受注"] -->|"customer"| C["顧客"]
C -->|"customer_name"| N["名前"]
O -.->|"合成した経路"| N
図5: 2つの矢印をつなぐと、受注から顧客の名前への1本の経路になる。
ここまでは、外部キーを持つ表を言い換えただけに見えます。違いが出るのは、次の節のようにスキーマどうしの関係を扱うときです。
スキーマを変えたときにデータを移す
業務システムを長く使っていると、スキーマは変わります。ここでは、顧客に「地域」を足す変更を考えます。新しいスキーマには地域の表と、顧客から地域への外部キーが増えます。古いスキーマの表と外部キーは、すべて新しいスキーマにそのまま含まれます。
この「古いスキーマは新しいスキーマにそのまま含まれる」という関係が、古いスキーマの圏から新しいスキーマの圏への関手 F です。Spivak の論文は、スキーマの間の関手からデータの移し方が3つ決まることを示し、それらを Δ(デルタ)、Σ(シグマ)、Π(パイ)と呼んでいます13。この記事ではそのうち Δ と Σ を試しました。以下、図とコードのコメントでは、古いスキーマを旧スキーマ、新しいスキーマを新スキーマと書きます。
flowchart TB
accTitle: 旧スキーマから新スキーマへの関手
accDescr: 旧スキーマの顧客・商品・受注とその外部キーが、関手 F によって新スキーマの同じ名前の表と外部キーに対応し、新スキーマには地域の表と顧客から地域への外部キーが増えていることを示す。
A["旧スキーマ:顧客・商品・受注"] -->|"関手 F"| B["新スキーマ:顧客・商品・受注"]
B -.- R["増えた表:地域"]
B -.- K["増えた外部キー:顧客 → 地域"]
図6: 旧スキーマの表と外部キーは、関手 F で新スキーマの同じ名前のものに対応する。
Δ で新しいデータを古いスキーマで読む
Δ は新しいスキーマのデータを、古いスキーマのデータとして読む移し方です。新しいアプリが地域を書き込むようになっても、古い帳票のプログラムは古いスキーマのままで読みたいという場面に当たります。ここからのコードは最初の例に続けて同じ Julia のセッションで実行します。新しいスキーマは <: で古いスキーマを広げて書き、データを入れる型 OrdersV2 も作ります。
# 新スキーマ: 顧客に地域(Region)を足した
@present SchOrdersV2 <: SchOrders begin
Region::Ob
region::Hom(Customer, Region)
region_name::Attr(Region, Name)
end
@acset_type OrdersV2(SchOrdersV2)
関手 F は同じ名前の表、外部キー、列どうしを対応させて作りました。FinCat はスキーマを圏として扱います。ob_generators はスキーマに書いた表と値の型を、hom_generators は外部キーと列を返します。これらをスキーマの生成元と呼び、圏の射は生成元をつないだ経路です。
FinFunctor で作るだけでは、対応が関手の条件を満たすかは確かめられません。外部キー customer(受注 → 顧客)を region(顧客 → 地域)に送る誤った対応 G も、エラーなく作れてしまいます。G では矢印の両端が合いません。is_functorial を呼ぶと、F は関手で、G は関手でないことが分かります。
# 旧スキーマを新スキーマへ埋め込む関手 F(同じ名前の生成元どうしを対応させる)
C, D = FinCat(SchOrders), FinCat(SchOrdersV2)
in_D(gens) = Dict(nameof(g) => g for g in gens)
Dob, Dhom = in_D(ob_generators(D)), in_D(hom_generators(D))
F = FinFunctor(
Dict(x => Dob[nameof(x)] for x in ob_generators(C)),
Dict(f => Dhom[nameof(f)] for f in hom_generators(C)),
C, D; homtype=:generator)
println("F は関手か: ", is_functorial(F))
# わざと誤った対応 G: 外部キー customer(受注 → 顧客)を region(顧客 → 地域)に送る
G = FinFunctor(
Dict(x => Dob[nameof(x)] for x in ob_generators(C)),
Dict(f => Dhom[nameof(f) == :customer ? :region : nameof(f)] for f in hom_generators(C)),
C, D; homtype=:generator)
println("G は関手か: ", is_functorial(G))
F は関手か: true
G は関手か: false
新しいスキーマに、関東と九州の2つの地域を持つデータを入れ、Δ で古いスキーマへ移しました。
# 新スキーマのデータ
v2 = @acset OrdersV2{String, Int, Int} begin
Region = 2
region_name = ["関東", "九州"]
Customer = 2
customer_name = ["A社", "B社"]
region = [1, 2]
Product = 2
product_name = ["ねじ", "ナット"]
price = [12, 8]
Order = 3
customer = [1, 2, 1]
product = [1, 1, 2]
quantity = [100, 50, 200]
end
# Δ_F: 新スキーマのデータを旧スキーマで読む(地域は落ちる)
old = migrate(Orders{String, Int, Int}, v2, DeltaMigration(F))
println("Δ で旧スキーマへ: 顧客 $(nparts(old, :Customer)) 件、受注 $(nparts(old, :Order)) 件")
println("顧客名: ", old[:customer_name])
println("旧スキーマの表: ", objects(acset_schema(old)))
Δ で旧スキーマへ: 顧客 2 件、受注 3 件
顧客名: ["A社", "B社"]
旧スキーマの表: [:Customer, :Product, :Order]
顧客と受注はそのまま残り、地域の表は移した先にありません。Δ は関手 F で古いスキーマに対応する部分だけを取り出します。新しいスキーマにしかない地域は、古いスキーマでは読めないので落ちます。
Σ で古いデータを新しいスキーマへ移す
Σ は逆向きで、古いスキーマのデータを新しいスキーマのデータへ移します。古いデータには地域がないので、Σ は顧客ごとに中身の分からない地域を作ります。
flowchart TB
accTitle: Δ と Σ の向き
accDescr: 関手 F が旧スキーマから新スキーマへ向かうとき、Δ は新スキーマのデータを旧スキーマのデータへ移し、Σ は旧スキーマのデータを新スキーマのデータへ移すことを示す。
F["関手 F:旧スキーマ → 新スキーマ"] --> D["Δ:新しいデータ → 旧スキーマ"]
F --> S["Σ:古いデータ → 新スキーマ"]
図7: 関手 F の向きは1つでも、データは Δ と Σ で両方の向きに移せる。
最初の例の db を、そのまま Σ で移しました。
# Σ_F: 旧スキーマのデータ db(最初の例)を新スキーマへ移す
Σ = SigmaMigrationFunctor(F, Orders{String, Int, Int}, OrdersV2{String, Int, Int})
# 値の決まらない列を AttrVar で表せる圏を、移す先として渡す
cat = ACSetCategory(VarACSetCat(OrdersV2{String, Int, Int}()))
moved = Σ(db; cat)
println("地域: ", nparts(moved, :Region), " 件")
println("顧客 → 地域: ", moved[:region])
println("地域名: ", moved[:region_name])
println("顧客名: ", moved[:customer_name])
println("受注 → 顧客: ", moved[:customer])
# Σ で移したデータを Δ で旧スキーマに戻すと、元の db と一致する
back = migrate(Orders{String, Int, Int}, moved, DeltaMigration(F))
println("Δ で戻すと元のデータと同じか: ", back == db)
地域: 2 件
顧客 → 地域: [1, 2]
地域名: AttrVar[AttrVar(1), AttrVar(2)]
顧客名: ["A社", "B社"]
受注 → 顧客: [1, 2, 1]
Δ で戻すと元のデータと同じか: true
キーワード引数 cat を付けずに Σ(db) と呼ぶと、MethodError: no method matching app(...) というエラーになります。Catlab 0.17 は acset をどの種類の圏の中で扱うかをデータから推し量らず、利用者が渡す設計に変わりました15。渡さないと Catlab.jl が推し量りますが、ここでは移す先の空のデータから、値の決まらない列を表せない種類の圏が選ばれます。Σ で新しく作る地域には名前の値がないので、その圏では結果を表せません。Catlab 0.17.6 のテストにも、属性を持つ acset の Σ には値の変数を持てる VarACSet の圏が要るという注記があります16。それにならって cat を渡しました。
地域は2件できて、A社(顧客1)は地域1、B社(顧客2)は地域2を指しています。地域名の AttrVar(1) と AttrVar(2) は値の決まっていない変数です。これが、先に書いた「中身の分からない地域」です。古いデータには「A社とB社が同じ地域か」という情報がないので、Σ は顧客ごとに別の地域を作りました。地域を1つにまとめる理由がない限り、まとめないのが Σ の移し方です。どの地域なのかは、移したあとに人やほかの処理が埋めることになります。顧客名、単価、数量など元からある値はそのまま残り、Σ で移したデータを Δ で古いスキーマに戻すと、元の db と一致しました。
flowchart TB
accTitle: Σ で作られた地域
accDescr: Σ で移すと、A社とB社の顧客それぞれに名前の決まっていない地域が1つずつ作られ、2つの地域が同じかどうかは決めないことを示す。
A["顧客:A社"] --> R1["地域1(名前は未定)"]
B["顧客:B社"] --> R2["地域2(名前は未定)"]
R1 -.- Q["同じ地域かは決めない"]
R2 -.- Q
図8: Σ は顧客ごとに地域を1つずつ作り、同じ地域かどうかは決めない。
この2つの例は SQL のマイグレーションの代わりになるものではありません。実際のデータベースのスキーマ変更は、これまでどおり番号付きのマイグレーションで行います。その進め方は「業務アプリのDBスキーマをバージョン管理する」で扱いました。圏論の道具が役に立つのは、新旧のスキーマの対応を関手として一度書けば、どちらの向きにどう移るかがその対応から決まる点です。移行の仕様を人が文章で書く代わりに、対応そのものをコードで書き、関手になっているかを is_functorial で確かめられます。
部品のモデルをつないで大きなモデルを作る
圏論のもう一つの使い道は合成です。小さな部品を作り、つなぎ方を決めて大きなものを組み立てるときに、部品だけでなくつなぎ方も正確に書けます。ここでは、受注部門の業務の流れと物流部門の業務の流れを部品にして、1つの流れにつなぎました。
部品は「開いた」グラフです。頂点が業務の状態、辺が状態の移り変わりで、外とつなげる頂点を決めておきます。受注部門の流れは「受付 → 在庫確認 → 出荷待ち」、物流部門の流れは「出荷待ち → 梱包 → 発送」です。Catlab.jl はこうした開いたグラフを構造化コスパン(structured cospan)として扱います17。構造化コスパンは外とつなげる部分を持つ部品を表し、部品どうしを組み合わせるための圏論の道具です18。外とつなげる部分は、コスパンの脚(leg)が指す頂点です。
flowchart TB
accTitle: 開いたグラフとしての受注部門の流れ
accDescr: 受注部門の流れの受付、在庫確認、出荷待ちの3つの状態のうち、受付が1本目の脚、出荷待ちが2本目の脚の指す頂点として、外とつながる点になることを示す。
L1["1本目の脚"] -.-> A["受付"]
A --> B["在庫確認"]
B --> C["出荷待ち"]
L2["2本目の脚"] -.-> C
図9: 受注部門の流れは、受付と出荷待ちを外とつなげる点として持つ開いたグラフになる。
まず、頂点に名前を持つグラフの型と、その開いたグラフの型を作ります。OpenACSetTypes は acset の型と、外とつなげる部分(ここでは頂点の V)から、開いた acset の型を作ります。OpenNamedGraph は Catlab.jl が用意している型ではなく、ここで作った型です。関数 flow は状態の名前の列から1本道のグラフを作ります。
using Catlab
# 頂点に名前を持つグラフ(業務の状態)と、その「開いた」版
@present SchNamedGraph <: SchGraph begin
Name::AttrType
vname::Attr(V, Name)
end
@acset_type NamedGraph(SchNamedGraph, index=[:src, :tgt])
# 頂点(V)で外とつなぐ開いたグラフの型。中身は構造化コスパン
const OpenNamedGraphOb, OpenNamedGraph = OpenACSetTypes(NamedGraph, :V)
# 状態の名前の列から、1本道のグラフを作る
function flow(names)
g = NamedGraph{String}()
add_parts!(g, :V, length(names), vname=names)
add_parts!(g, :E, length(names) - 1, src=1:length(names)-1, tgt=2:length(names))
g
end
次に、2つの部品を作ります。FinFunction([1], 3) と FinFunction([3], 3) は3つの頂点のうち1番目と3番目を、それぞれ1本目と2本目の脚が指す頂点にする指定です。
# 受注部門の流れ: 受付 → 在庫確認 → 出荷待ち(1本目の脚は「受付」、2本目の脚は「出荷待ち」)
order = flow(["受付", "在庫確認", "出荷待ち"])
order_open = OpenNamedGraph{String}(order, FinFunction([1], 3), FinFunction([3], 3))
# 物流部門の流れ: 出荷待ち → 梱包 → 発送
ship = flow(["出荷待ち", "梱包", "発送"])
ship_open = OpenNamedGraph{String}(ship, FinFunction([1], 3), FinFunction([3], 3))
つなぎ方は @relation で書きます。order(a, b) と ship(b, c) が同じ b を共有しているので、受注部門の2本目の脚と物流部門の1本目の脚がつながります。これは無向配線図(undirected wiring diagram)で、線に向きはありません。どちらを入口と呼ぶかは、脚の順番で決めているだけです。oapply が配線図に部品を当てはめて、1つのモデルを作ります。
# 2つを「出荷待ち」でつなぐ合成のしかたを、配線図で書く
seq = @relation (a, c) where (a, b, c) begin
order(a, b)
ship(b, c)
end
whole = apex(oapply(seq, Dict(:order => order_open, :ship => ship_open)))
println("状態: ", whole[:vname])
for e in parts(whole, :E)
println(whole[whole[e, :src], :vname], " → ", whole[whole[e, :tgt], :vname])
end
状態: ["受付", "在庫確認", "出荷待ち", "梱包", "発送"]
受付 → 在庫確認
在庫確認 → 出荷待ち
出荷待ち → 梱包
梱包 → 発送
2つの部品はそれぞれ「出荷待ち」を持っていましたが、つないだ結果では「出荷待ち」は1つになり、状態は5つになりました。
flowchart TB
accTitle: 2つの部門の流れをつなぐ
accDescr: 受注部門の流れの出口の出荷待ちと、物流部門の流れの入口の出荷待ちを、配線図の共有する端 b でつなぎ、受付から発送までの1つの流れになることを示す。
O["受注部門:受付 → 在庫確認 → 出荷待ち"] --> W["配線図:端 b を共有"]
S["物流部門:出荷待ち → 梱包 → 発送"] --> W
W --> R["受付 → … → 出荷待ち → … → 発送"]
図10: 2つの部品は、配線図で共有する端の「出荷待ち」でつながる。
つなげる点が合わない部品は、つなげません。物流部門の流れを「出荷準備」から始まるものにすると、oapply はエラーを出します。
# つなぐ点の名前が合わない部品(「出荷準備」から始まる)は、つなげない
prep = flow(["出荷準備", "梱包", "発送"])
prep_open = OpenNamedGraph{String}(prep, FinFunction([1], 3), FinFunction([3], 3))
try
oapply(seq, Dict(:order => order_open, :ship => prep_open))
catch e
println("つなげない: ", first(split(sprint(showerror, e), ':')))
end
つなげない: Feet of cospans are not equal
足(feet)は部品の外とつなげる点だけを取り出したものです。エラーの全文には、「出荷待ち」の足と「出荷準備」の足が一致しないことが出ています。配線図と部品が食い違えば、組み立てる前に分かります。
この例は単純ですが、同じ作り方はもっと大きなモデルでも使われています。John Baez らの論文は AlgebraicJulia のパッケージ StockFlow で疫学のストック・フロー図を部品から組み立てる方法を示し、カナダで使われた COVID-19 のモデルを簡単にした例で説明しています19。部品の構造と、そこから作る微分方程式とを分けて扱うのが特徴です。この論文は部品を表すのに、構造化コスパンに近い道具の decorated cospan を使い、つなぎ方はこの記事と同じ無向配線図で書いています。CatColab にも、ストック・フロー図やペトリネットのロジックがあります9。
業務システムで使うときに考えること
試してみると、考え方は受注のスキーマや業務の流れにそのまま当てはめられました。一方で、ライブラリは業務の開発者がすぐ使える形にはなっていない、というのが私の感想です。Catlab.jl の README は、Catlab が主にプログラミングのライブラリであり、グラフィカルなツールや定理証明器ではないと書いています7。AlgebraicJulia のサイトは、Catlab は 1.0 に近い段階にあり、ほかのパッケージは API が変わりうると書いています5。この記事でも、関手の作り方とデータ移行の呼び出し方は、手元の Catlab 0.17.6 のテストと移行ガイドを読んで合わせる必要がありました。属性を持つ表の Σ は既定の呼び方ではエラーになり、移す先の圏を渡して初めて動きました。
flowchart TB
accTitle: 業務で使う場面の切り分け
accDescr: 新旧のスキーマの対応を書きたいときや、部品からモデルを組み立てたいときは Catlab.jl で試し、実際のスキーマの変更はこれまでどおりマイグレーションで行い、プログラムを書かない人とモデルを共有するなら CatColab を見ることを示す。
Q["やりたいこと"] --> A["スキーマの対応を書く"]
Q --> B["部品からモデルを作る"]
Q --> C["実際のスキーマを変える"]
Q --> D["書かない人とモデルを共有"]
A --> T["Catlab.jl で試す"]
B --> T
C --> M["従来のマイグレーション"]
D --> CC["CatColab を見る"]
図11: 圏論の道具は対応やつなぎ方を書く場面で試し、実際のスキーマの変更は従来の方法で行う。
業務システムでの使い道としては、次のような場面が考えられます。
- 複数のシステムのスキーマの対応を、関手として書いて確かめます。移行や連携の仕様を、文章ではなく
is_functorialで確かめられるコードとして残せます。 - 部門ごとに作ったモデルを、つなぎ方を明示して1つにまとめます。どこでつながっているかが図とコードの両方に残り、つなげる点が合わなければエラーになります。
- プログラムを書かない人と概念モデルを共有します。CatColab が目指している使い方ですが、この記事では試していません。
まずは小さな例で、対応やつなぎ方を書いてみるところから始められます。
試した環境と確かめていないこと
例は2026年11月12日に、次の環境のコンテナで実行しました。
- Ubuntu 24.04.5 LTS(Linux 6.18)
- Julia 1.11.7(julialang.org の公式のバイナリを、公開されているチェックサムで確かめて使用)
- Catlab 0.17.6(依存する ACSets 0.2.29、GATlab 0.2.4)
受注の例はスキーマから Σ までのコードを1つの Julia のセッションで順に実行しました。合成の例は別のセッションで実行しました。記事に載せたコードは実行したものの全文で、出力も実行したときのものです。同じコードと、依存するパッケージの版を固定した Project.toml・Manifest.toml、期待する出力を komurasoft-blog-samples の topos-institute-category-theory-business-examples で公開しています。
確かめていないことは次のとおりです。
- Π による移し方。
- Windows での実行。
- CatColab の操作。公式のヘルプとソースの説明の範囲で紹介しています。
- StockFlow など、Catlab.jl 以外の AlgebraicJulia のパッケージ。論文の範囲で紹介しています。
関連記事
参考リンク
-
Topos Institute, Legal information. 米国カリフォルニア州の非営利団体 Topos Institute と、英国の慈善団体 Topos Research UK の、法的に独立した2つの組織があること。 ↩
-
Topos Institute, Topos Institute. 使命にもとづく非営利の研究所であることと、使命の説明(”We research, build, and serve others through new technologies that enable cooperation across difference.”)。 ↩
-
Topos Institute, Team. CEO の Brendan Fong と、Senior Scientist の David Spivak など、チームの構成。 ↩
-
AlgebraicJulia, AlgebraicJulia. 応用圏論にもとづく科学技術計算を目指すことと、Catlab.jl ほかのパッケージの開発状況。 ↩ ↩2
-
Kevin Carlson, Introducing CatColab. Topos Institute のブログ(2024年10月2日)。自分たちや仲間の数学の考えをソフトウェアにすることが主な仕事の一つで、これまでは主に AlgebraicJulia だったこと、AlgebraicJulia の学びにくさから近づきやすいツールを自分たちで作ると決めたこと、圏論の部分を Rust で書き WebAssembly にしてブラウザで動かしていること、最初の版(0.1)の発表。 ↩ ↩2
-
AlgebraicJulia, Catlab.jl. Julia 言語で応用圏論を扱うための枠組みであること、MIT ライセンス、主にプログラミングのライブラリであること。 ↩ ↩2
-
Topos Institute, CatColab Help. 形式的で相互運用できる概念的なモデリングのための共同作業環境であること、Topos Institute とその協力者が作った技術であること、開発中のため重要なデータを保存しないよう求める注意書き。 ↩ ↩2
-
Topos Institute, CatColab Logics. Olog、データベースのスキーマ、ペトリネット、因果ループ図、ストック・フロー図などのロジックの一覧。 ↩ ↩2
-
Topos Institute, CatColab. ソースと、MIT と Apache-2.0 のデュアルライセンス。 ↩
-
Brendan Fong, David I. Spivak, Seven Sketches in Compositionality: An Invitation to Applied Category Theory. データベースや電気回路などの応用ごとに圏論の考え方を紹介する入門書。 ↩
-
Nelson Niu, David I. Spivak, Polynomial Functors: A Mathematical Theory of Interaction. 多項式関手で相互作用の仕組みや力学系をモデル化する本。 ↩
-
David I. Spivak, Functorial Data Migration. スキーマを小さな圏、データを集合に値をとる関手として扱い、スキーマの間の射から3つのデータ移行の関手が決まること。 ↩ ↩2
-
Evan Patterson, Owen Lynch, James Fairbanks, Categorical Data Structures for Technical Computing. acset(attributed C-set)が C-set にデータの属性を加えたもので、グラフとデータフレームの両方を一般化すること。Compositionality 第4巻(2022年)。 ↩
-
AlgebraicJulia, Catlab.jl v0.17.6 Release Notes(docs/src/devdocs/release.md). v0.17 への移行ガイド。acset をどの種類の圏で扱うかは acset から推し量らず、原則として渡すべきデータであること、
VarACSetCatが属性の変数を持てる圏であること、catキーワード引数。 ↩ -
AlgebraicJulia, Catlab.jl v0.17.6 test/categorical_algebra/pointwise/acsetcats/FunctorialDataMigrations.jl. 属性を持つ acset の Σ には VarACSet の圏が要り、自由に作られる対象の属性には属性の変数が割り当てられるという注記。 ↩
-
AlgebraicJulia, Catlab.jl v0.17.6 src/categorical_algebra/pointwise/StructuredCospans.jl. 構造化コスパンの汎用のインターフェースと acset での実装、
OpenACSetTypes。 ↩ -
John C. Baez, Kenny Courser, Structured Cospans. 入力と出力の接点を持つ開いたシステムをモデル化し、組み合わせて大きなシステムにする方法。Theory and Applications of Categories 第35巻(2020年)。 ↩
-
John Baez, Xiaoyan Li, Sophie Libkind, Nathaniel D. Osgood, Evan Patterson, Compositional Modeling with Stock and Flow Diagrams. StockFlow で疫学のストック・フロー図を部品から組み立て、構造と微分方程式の意味を分けて扱うこと。合成は decorated cospan で与え、無向配線図のオペラッドで書くこと。カナダで使われた COVID-19 のモデルを簡単にした例。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
業務システムのコード設計 ── 商品コード・顧客コードの決め方とチェックディジット
商品コード・顧客コードなど業務システムのコード体系を決める実践ガイド。有意コードと無意味連番の判断表、JAN・Luhn等のチェックディジット算式とC#実装、Excelの0落ち対策、桁あふれと移行まで整理します。
マルチスレッドの実務ベストプラクティス Java編 ── 仮想スレッド時代の定石
Javaのマルチスレッドはスレッドを直接作らずExecutorServiceと仮想スレッドに乗るのが定石。synchronizedとReentrantLockの使い分け、割り込みによる協調停止、ConcurrentHashMapの原子操作、SwingのEDTまで実務の原則を...
マルチスレッドの実務ベストプラクティス C言語編 ── Win32 APIの流儀で安全に書く
C言語×Win32のマルチスレッドは、_beginthreadexでのスレッド作成、SRWロックと条件変数、Interlocked、停止イベント+WaitForMultipleObjectsの停止設計が定石。TerminateThreadの危険とDllMainの制約まで整理...
マルチスレッドの実務ベストプラクティス C++編 ── RAIIとjthreadで事故を構造から消す
C++のマルチスレッドはデータ競合が未定義動作になる世界。std::threadのデストラクタの罠、jthreadとstop_tokenによる停止設計、scoped_lockのデッドロック回避、atomicの正しい位置づけ、Win32同期APIとの使い分けまで整理します。
マルチスレッドの実務ベストプラクティス .NET編 ── スレッドを増やす前に決めておくこと
「スレッドを立てたら、たまに落ちる・固まる」を防ぐ設計の定石を.NET/C#向けに整理。スレッドを自分で作らずTaskに乗る、共有可変状態を減らす、ロックの規律、CancellationTokenでの停止設計、UIスレッドの扱いまで解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
技術相談・設計レビュー
業務システムのスキーマの変更やデータの移行で、新旧のスキーマの対応をどう決めて検証するかを整理したい場合に、技術相談・設計レビューとして扱えます。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- 圏論は業務のソフトウェアで何に使えますか?
- データベースのスキーマ、スキーマ間のデータ移行、部品のモデルの合成が代表的な使い道です。この記事で扱うTopos Instituteは、JuliaのライブラリCatlab.jlを含むAlgebraicJuliaの開発にかかわり、ブラウザのモデリングツールCatColabを公開しています。この記事ではCatlab.jlで小さな例を動かしました。
- 圏論を使うと、SQLのマイグレーションは要らなくなりますか?
- なりません。この記事の例は、新旧のスキーマの対応を関手として書き、その対応からデータの移し方が決まることを示すものです。実際のデータベースの変更は、これまでどおりマイグレーションで行います。
- Catlab.jlはすぐに業務で使えますか?
- Catlab.jlは1.0に近い段階とされていますが、この記事でも呼び出し方を手元の版のテストと移行ガイドで確かめる必要がありました。たとえば属性を持つ表をΣで移すには、移す先の圏を引数で渡さないとエラーになります。考え方を試す道具としては十分使えますが、本番のシステムに組み込むなら版を固定して検証が要ります。