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
初めてのLean言語
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
NearMeの技術発表資料です
PRO
June 19, 2026
Technology
10
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
初めてのLean言語
NearMeの技術発表資料です
PRO
June 19, 2026
More Decks by NearMeの技術発表資料です
See All by NearMeの技術発表資料です
Apache Airflow Workflow orchestration without turning cron into spaghetti
nearme_tech
PRO
1
16
実務で役立つ幾何学 ボロノイ図の基礎から グラフ・ネットワーク応用まで
nearme_tech
PRO
1
48
SQL/ID抽出タスクから考える 実践的なハルシネーション対策
nearme_tech
PRO
1
58
OpenCode & Local LLM
nearme_tech
PRO
0
110
OpenCode Introduction
nearme_tech
PRO
0
52
【Browser Automation × AI】 Stagehandを試してみよう
nearme_tech
PRO
0
140
AIを用いた PID制御で部屋 の温度制御をしてみた
nearme_tech
PRO
0
140
CopilotKit + AG-UIを学ぶ
nearme_tech
PRO
3
560
Tile38 Overview
nearme_tech
PRO
0
100
Other Decks in Technology
See All in Technology
"何を作るか"を任される エンジニアは、どう育つのか
yutaokafuji
1
580
白金鉱業Meetup_Vol.24_「AIエージェントは分けるほど良い」は本当か? / Is it true that “the more you divide AI agents, the better”?
brainpadpr
1
240
作って終わりにしない タイミーのセマンティックレイヤー育成の現在地
chanyou0311
3
2.1k
AIを「創る」と「使う」の循環 — HRテックが実践するリアルなAI組織実装
taketo957
0
1.9k
Oracle AI Database@Google Cloud:サービス概要のご紹介
oracle4engineer
PRO
6
1.5k
protovalidate-es を導入してみた
bengo4com
0
170
RSA暗号を手計算したくなること、ありますよね?? (20260615_orestudy6_rsa)
thousanda
0
170
2026TECHFRESH畢業分享會 - Lightning Talk - E起 See See : 電商推薦讀心術? 數據說了算
line_developers_tw
PRO
0
680
「エンジニア進化論」2028年の開発完全自動化、エンジニアはどう進化するか
cyberagentdevelopers
PRO
4
4.3k
2026TECHFRESH畢業分享會 - AI 時代的人生存檔點
line_developers_tw
PRO
0
700
日本 Fintech 未来予測レポート 2027〜2028年(手動編集版)
8maki
0
1.4k
Disciplined Vibes: Scaling AI-Assisted Engineering
sheharyar
0
120
Featured
See All Featured
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
140
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Automating Front-end Workflow
addyosmani
1370
210k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
250
The Limits of Empathy - UXLibs8
cassininazir
1
350
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
How STYLIGHT went responsive
nonsquared
100
6.2k
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
160
Practical Orchestrator
shlominoach
191
11k
エンジニアに許された特別な時間の終わり
watany
107
250k
Being A Developer After 40
akosma
91
590k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
330
Transcript
0 初めてのLean⾔語 2026-06-19 第149回NearMe技術勉強会 Kenji Hosoda
1 Leanについて • Leanは、定理証明⽀援系と呼ばれるプログラミング⾔語の⼀種 • 型理論に基づいた強⼒な型システムを持ち、 プログラムの正当性を形式的に保証することができる https://lean-lang.org/
2 なぜ今、Leanを学ぶのか • AIが数学の未解決問題を解いたことが話題になり、 そこで、Leanという⾔語が使われていて、純粋に興味深い • プロダクト開発においても、AI+Leanの組み合わせで、 より安全なコードが書けると期待されている
3 Leanのインストール • Lean 4 VS Code Extensionを以下のURLからインストール ◦ https://marketplace.visualstudio.com/items?itemName=leanprover.lean4
▪ インストールマニュアル :https://github.com/leanprover/vscode-lean4/blob/master/vscode-lean4/ manual/manual.md • CLIでインストールしたい⽅はこちらを参考(もしくはVSCodeがうまく⾏かない場合) ◦ https://lean-ja.github.io/lean-by-example/HowToInstall.html
4 Getting Started • Plus.lean ファイルを作成 • 以下のコードを記載 ◦ def
onePlusOneIsTwo : 1 + 1 = 2 := rfl ▪ 1 + 1 = 2という命題が正しいことを⽰す定義 ▪ rfl は反射律(reflexivity) t = t の証明 • 左辺と右辺を定義展開‧簡約し、両者が定義上等しいと判断できる場合には、 それらを同じ式として扱う。 • コンパイルが成功するか確認 ◦ VSCode:ファイルを開いた時にコード上にエラーがない ◦ CLI:“lean Plus.lean” コマンドを実⾏してエラーがない • 以下のコードを追加して、コンパイルが失敗することを確認 ◦ def onePlusOneIsFifteen : 1 + 1 = 15 := rfl
5 初めての証明 • ⾃然数の和が以下のようになることを証明 (ガウスの公式) • 以下から SumOfNaturalNumbers.lean をダウンロードしてコンパイル ◦
https://gist.github.com/kenji4569/668aac13a4249e983fec3b019aa35e51
6 初めての証明:⾃然数の和を定義 def sum_of_natural_numbers (n : Nat) : Nat :=
-- 自然数の和を再帰で定義する。 match n with -- 0 までの和。 | 0 => 0 -- n + 1 までの和は、n までの和に n + 1 を足す。 | n + 1 => sum_of_natural_numbers n + (n + 1) -- 計算例。 #eval sum_of_natural_numbers 5
7 初めての証明:帰納法で証明1/2 theorem sum_of_natural_numbers_formula (n : Nat) : -- 示したい公式。
2 * sum_of_natural_numbers n = n * (n + 1) := by -- n について帰納法。 -- `induction ... with` は場合分けしながら帰納法を始める。 induction n with | zero => -- 基底部。 -- `simp` は基本的な定義や定理で式を自動的に簡約する。 simp [sum_of_natural_numbers] ...続く
8 ...続き | succ n ih => -- `succ` は「次の自然数」の場合、つまり
`n + 1` のケース。 -- `ih` は induction hypothesis の略で、帰納法の仮定。 -- ih : 2 * sum_of_natural_numbers n = n * (n + 1) -- `calc` は等式変形を1行ずつつないで書く。 calc 2 * sum_of_natural_numbers (n + 1) -- `sum_of_natural_numbers` を展開し、`left_distrib`(乗法の左分配法則)を使う。 = 2 * sum_of_natural_numbers n + 2 * (n + 1) := by simp [sum_of_natural_numbers, Nat.left_distrib] -- 帰納法の仮定で置き換える。 _ = n * (n + 1) + 2 * (n + 1) := by -- `rw` は等式を使って式を書き換える。 rw [ih] -- `add_mul`(乗法の右分配法則)でまとめ、`add_comm`(加法の交換法則)で順序をそろえる。 _ = (n + 2) * (n + 1) := by rw [← Nat.add_mul, Nat.add_comm n 2] -- `mul_comm`(乗法の交換法則)で右辺の形に合わせる。 _ = (n + 1) * (n + 2) := by rw [Nat.mul_comm] 初めての証明:帰納法で証明2/2
9 信号機の例 • 以下から TrafficLight.lean をダウンロードしてコンパイル ◦ https://gist.github.com/kenji4569/9c1cc2e9b9d28ecf147363e8c03104f6
10 -- 信号の色を表す型を定義する。 -- `inductive` は、場合の候補を並べて新しい型を作る。 inductive Light | red
| yellow | green -- 次の信号の色を返す関数。 def next | Light.red => Light.green | Light.green => Light.yellow | Light.yellow => Light.red 信号機の例:状態と状態遷移を定義
11 theorem red_never_direct_to_yellow : next Light.red ≠ Light.yellow := by
-- `simp` は定義を展開して式を簡単にする。 simp [next] theorem next_three_times_returns_same (l : Light) : next (next (next l)) = l := by -- `cases l` は `l` を `red` `yellow` `green` の各場合に分ける。 -- `<;>` は、右側の tactic をすべてのゴールに適用する。 -- `rfl` は左右が定義上同じときに証明を閉じる。 cases l <;> rfl 信号機の例:挙動を証明
12 配⾞システムの状態遷移の例 • 以下から RideStateMachine.lean をダウンロードしてコンパイル ◦ https://gist.github.com/kenji4569/3957e68fd888d5e57876b9533d5aff0a
13 配⾞システムの状態遷移の例:状態の定義 inductive RideStatus | requested | rejected | assigned
| pickedUp structure Driver where name : String -- Ride は現在の状態と、必要なら担当ドライバーを持つ。 -- `driver : Option Driver` の `Option` は -- 「値があるかもしれないし、ないかもしれない」型。 structure Ride where status : RideStatus driver : Option Driver
14 配⾞システムの状態遷移の例:状態遷移の定義 -- 配車リクエスト直後は、状態は requested でドライバーは未定。 def requestRide : Ride
:= { status := .requested, driver := none } -- requested のときだけドライバーを割り当てられる。 -- 成功すると `some Ride`、不正な状態なら `none` を返す。 -- `some x` は「値がある」ケースで、 `x` を包んだ値。 -- `none` は「値がない」ケース。 def assignDriver (ride : Ride) (driver : Driver) : Option Ride := match ride.status with | .requested => some { ride with status := .assigned, driver := some driver } | _ => none -- requested のときだけ rejected に進める。 -- reject しても driver は割り当てられない。 def rejectRide (ride : Ride) : Option Ride := match ride.status with | .requested => some { ride with status := .rejected, driver := none } | _ => none -- assigned のときだけ pickedUp に進める。 def pickUp (ride : Ride) : Option Ride := match ride.status with | .assigned => some { ride with status := .pickedUp } | _ => none
15 配⾞システムの状態遷移の例:挙動の証明 -- requested からは reject できる。 theorem request_can_be_rejected :
rejectRide requestRide = some { status := .rejected, driver := none } := by simp [requestRide, rejectRide] -- requested から、直接 pickedUp に進めることはできない。 theorem request_cannot_be_picked_up_directly : pickUp requestRide = none := by simp [requestRide, pickUp] -- requested から assigned にドライバーを割り当てられる。 theorem request_ride_can_be_picked_up_with_driver (driver : Driver) : -- `>>=` は bind と呼ばれる演算子。 -- ここでは `Option` 用の bind なので、次のように動く: -- `some ride >>= pickUp` は `pickUp ride` -- `none >>= pickUp` は `none` -- つまり「assignDriver が成功したときだけ、その結果を pickUp に渡す」。 (assignDriver requestRide driver >>= pickUp) = some { status := .pickedUp, driver := some driver } := by simp [requestRide, assignDriver, pickUp]
16 配⾞システムの状態遷移の例: 全状態に対する証明(1/4) • 「pickedUp なら、既にドライバーが割り当 てられている」を証明する • まずは、定義した遷移を使って「どんな Ride
が到達可能か」を考える -- `Reachable ride` は -- 「その `ride` は、このファイルで定義した正しい遷移だけを使って到達できる」 -- という性質を表す述語。 -- -- `Ride` の型そのものは、例えば -- `{ status := .pickedUp, driver := none }` -- のような不正な値も作れてしまう。 -- そこで「ありうる Ride 全体」ではなく、 -- 「初期状態から正しい操作で到達した Ride」に話を限定する。 -- -- `Prop` は `proposition` の略で、「真か偽かを主張する命題の型」を表す。 -- つまり `Reachable : Ride -> Prop` は、各 `Ride` について -- 「その Ride は到達可能か」という命題を返す、という意味。 -- -- `inductive` で `Prop` を作ると、 -- 「どんなときに `Reachable ride` を証明してよいか」を -- コンストラクタで列挙できる。 inductive Reachable : Ride -> Prop where -- 初期状態 `requestRide` は到達可能。 | request : Reachable requestRide -- 既に到達可能な `ride` に対して、`rejectRide` が成功して `next` になったなら、 -- その `next` も到達可能。 | reject ( ride : Ride) ( next : Ride) : Reachable ride -> rejectRide ride = some next -> Reachable next -- 既に到達可能な `ride` に対して、`assignDriver` が成功して `next` になったなら、 -- その `next` も到達可能。 | assign ( ride : Ride) ( driver : Driver) ( next : Ride) : Reachable ride -> assignDriver ride driver = some next -> Reachable next -- 既に到達可能な `ride` に対して、`pickUp` が成功して `next` になったなら、 -- その `next` も到達可能。 | pickUp ( ride : Ride) ( next : Ride) : Reachable ride -> pickUp ride = some next -> Reachable next
17 配⾞システムの状態遷移の例: 全状態に対する証明(2/4) -- pickedUp なら、既にドライバーが割り当てられている。 theorem picked_up_has_driver {ride :
Ride} : Reachable ride -> ride.status = .pickedUp -> ∃ driver, ride.driver = some driver := by -- `->`(含意)が2つ並んでいるので、まず仮定を 1つずつ手元に取り出す。 -- `intro hreach` は「`Reachable ride` という仮定に `hreach` という名前を付けて -- 仮定欄(コンテキスト)に置く」操作。これで証明すべきゴールは -- 「ride.status = .pickedUp -> ∃ driver, ride.driver = some driver 」になる。 intro hreach -- ここがこの証明の山場。 -- いきなり「pickedUp なら driver がある」を帰納法で示そうとすると、 -- pickUp 遷移(assigned -> pickedUp )の場面で詰まる。 -- なぜなら pickUp の「遷移元」は pickedUp ではなく assigned なので、 -- 「pickedUp なら…」という帰納法の仮定がそこでは使えないから。 -- -- そこで、ゴールをあえて「 assigned か pickedUp なら driver がある」という -- 少し強い主張 h に置き換える。`suffices h : P by ...` は -- 「P さえ証明できれば元のゴールが従う。その『従う』部分を今ここで示す」という構文。 -- ここでは元の仮定 hstatus(pickedUp) を「assigned ∨ pickedUp」の右側 `Or.inr` に -- 入れて h に渡すだけで元のゴールが出る。 suffices h : ride.status = .assigned ∨ ride.status = .pickedUp → ∃ driver, ride.driver = some driver by intro hstatus exact h (Or.inr hstatus) -- これ以降のゴールは、上で宣言した強い主張 `h` の中身そのもの。 ...続く • 「assigned か pickedUp なら 既にドラ イバーが割り当てられている」という 少し強い証明をする • そこから「pickedUp なら、既にドライ バーが割り当てられている」を導く
18 配⾞システムの状態遷移の例: 全状態に対する証明(3/4) • Reachableな状態の場合わけ ◦ request ◦ reject ◦
…続く ...続き -- `induction hreach with` で「ride はどうやって到達可能になったのか」を -- Reachable のコンストラクタごとに場合分けする。 -- Reachable は request / reject / assign / pickUp の4通りで作られるので、 4ケースになる。 induction hreach with | request => -- ケース1: ride は初期状態 requestRide 。状態は requested 。 -- 強い主張の仮定「 assigned ∨ pickedUp 」を取り出すと … intro hstatus -- requestRide を展開すると status は .requested 。 -- 「.requested = .assigned ∨ .requested = .pickedUp 」はどちらも成り立たないので、 -- simp が矛盾(False)を見つけてゴールを閉じてくれる。 simp [requestRide] at hstatus | reject ride next _ hrej _ => -- ケース2: 直前の ride を rejectRide して next になった。 -- `hrej : rejectRide ride = some next` が手元にある。 intro hstatus -- rejectRide の定義(中身の match)を hrej に展開する。 unfold rejectRide at hrej -- `split` は hrej の中の `match ride.status with ...` を場合分けする。 -- 「requested だったとき」と「それ以外」の 2ケースに割れる。 split at hrej · -- requested だったとき : hrej は `some {status := rejected,..} = some next` 。 -- `injection` は「some a = some b なら a = b」を取り出す(コンストラクタの単射性)。 injection hrej with hnext -- hnext で next の中身が分かったので、ゴール中の next をその値に置き換える。 subst hnext -- next の状態は rejected 。assigned でも pickedUp でもないので hstatus は矛盾。 simp at hstatus · -- それ以外のとき : rejectRide は none を返すので hrej は `none = some next` 。 -- これはあり得ない( none と some は別物)ので contradiction で閉じる。 contradiction ...続く
19 配⾞システムの状態遷移の例: 全状態に対する証明(4/4) • Reachableな状態の場合わけ ◦ …続き ◦ assign ◦
pickUp ...続き | assign ride driver next _ hass _ => -- ケース3: 直前の ride に assignDriver driver して next になった。 -- `hass : assignDriver ride driver = some next` が手元にある。 -- 状態仮定は使わないので `_` で受け流す。 intro _ unfold assignDriver at hass split at hass · -- requested だったとき : next は driver := some driver で作られている。 injection hass with hnext subst hnext -- ゴール `∃ d, next.driver = some d` を満たす d として driver を提示。 -- `⟨driver, rfl ⟩` は ∃ の中身(証拠 driver と、その等式 rfl)の組。 -- next.driver はまさに `some driver` なので等式は rfl(自明な反射律)で済む。 exact ⟨driver, rfl⟩ · contradiction | pickUp ride next _ hpick ih => -- ケース4: 直前の ride を pickUp して next になった。 -- `hpick : pickUp ride = some next` 、 -- `ih` は帰納法の仮定 = 「直前の ride について、強い主張が成り立つ」。 intro _ unfold pickUp at hpick split at hpick · -- pickUp が成功するのは遷移元 ride が assigned のときだけ。 -- split が作った「 ride.status = .assigned 」という仮定に名前を付ける。 -- (split は名前を自動で付けるので、 `rename_i` で手前から名付け直す。 ) rename_i hassigned injection hpick with hnext subst hnext -- next.driver は ride.driver をそのまま引き継ぐ。 -- ride は assigned なので、帰納法の仮定 ih に「左側(assigned) 」を渡せば -- 「ride に driver がある」が得られる。あとは simp が next と ride の -- driver が同じことを使ってゴールに合わせてくれる (`simpa`)。 simpa using ih (Or.inl hassigned ) · contradiction ...続く
20 • 思ったより証明はかなり⻑かった • AIの補助がないと、(初⼼者なので)きつい • 逆に、AIの補助があれば、なんとか書ける • AIの出⼒でコンパイルエラーは何回かあった •
ただ、⾃⼰修正して証明まで辿り着けた • LeanとAIの共同作業といったところか • ⼀度、このような証明が組めれば、強⼒な品質保証になりうる 配⾞システムの状態遷移の例:全状態に対する証明の所感
21 • 分散システムにおけるLeader選出の証明 ◦ Leaderは⾼々1つ(同⼀Termで2⼈以上のLeaderは存在しない) ◦ 補題 ▪ 1ノードは1Termにつき1票しか投票しない ▪
Leaderになるには過半数の票が必要 ▪ 2つの過半数集合は必ず交差する • ドライバーへの順番通知で無限ループにならないことの証明 ◦ 以下のような状況で通知が無限ループする ▪ ドライバーAの通知の後、ドライバーBに通知 ▪ ドライバーBに通知の後、ドライバーCに通知 ▪ ドライバーCの通知の後、ドライバーAに通知 ◦ 通知フローの依存グラフに閉路が存在しないことを証明する その他Leanならではの応⽤例
22 参考サイト • 本家 ◦ https://lean-lang.org/ • Wikipedia ◦ https://ja.wikipedia.org/wiki/Lean_(%E8%A8%BC%E6%98%8E%E3%82%A2%E
3%82%B7%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%88) • lean-ja ◦ https://lean-ja.github.io/lean-by-example/index.html • 「証明⽀援系LEANに⼊⾨しよう」 ◦ https://speakerdeck.com/unaoya/zheng-ming-zhi-yuan-xi-leanniru-men-siyou
23 あわせて読みたい • 数学とAIのこれまで(とこれから) ◦ https://www.amazon.co.jp/dp/B0F4QR9RYM ◦ 証明⽀援系‧⾃動証明システムの歴史的背景や 研究の現場を知れる ◦
⽣成AIとの関連も書かれていて⾯⽩い
24 Thank you