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
NearMeの技術発表資料です
PRO
June 19, 2026
Technology
76
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の技術発表資料です
PosthogのA/Bテスト機能の紹介
nearme_tech
PRO
1
22
AIフレンドリーなプロダクトに向けて
nearme_tech
PRO
1
50
Apache Airflow Workflow orchestration without turning cron into spaghetti
nearme_tech
PRO
1
21
実務で役立つ幾何学 ボロノイ図の基礎から グラフ・ネットワーク応用まで
nearme_tech
PRO
1
59
SQL/ID抽出タスクから考える 実践的なハルシネーション対策
nearme_tech
PRO
1
68
OpenCode & Local LLM
nearme_tech
PRO
0
200
OpenCode Introduction
nearme_tech
PRO
0
60
【Browser Automation × AI】 Stagehandを試してみよう
nearme_tech
PRO
0
170
AIを用いた PID制御で部屋 の温度制御をしてみた
nearme_tech
PRO
0
180
Other Decks in Technology
See All in Technology
AIツールを導入しても生産性はあがらない? カオナビが直面した 3つの壁と乗り越え方。/ Overcoming 3 Barriers to AI-Driven Productivity at kaonavi
kaonavi
0
350
reFACToring
moznion
1
740
現場で使える AWS DevOps Agent 活用ノウハウ - Release Management 機能の検証結果を添えて / AWS DevOps Agent Release Management and Know-How
kinunori
3
250
エンタープライズデータへ安全につなぐ Production-ready なエージェント設計 ― AI × MCP リファレンスアーキテクチャ ― #AIDevDay
cdataj
1
170
AI Agent を本番環境へ―― Microsoft Foundry × Azure Serverless で作る Enterprise-Ready な基盤
shibayan
PRO
1
610
全社でのソフトウェアサプライチェーン攻撃対策をやってみた with Takumi Guard
z63d
0
320
Flutter研修【MIXI 26新卒技術研修】
mixi_engineers
PRO
1
220
Vポイント分析基盤におけるデータモデリング20年史
taromatsui_cccmkhd
4
780
どこまでAIに任せるか 〜確率論と決定論の境界決定〜
shukob
0
550
実践が先生だった— 新卒サーバーエンジニア1年目のリアル
mixi_engineers
PRO
0
150
Power Automateアップデート情報
miyakemito
0
260
人とエージェントが高め合う協業設計
kintotechdev
0
1k
Featured
See All Featured
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
560
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2.1k
Java REST API Framework Comparison - PWX 2021
mraible
34
9.6k
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
470
How to Ace a Technical Interview
jacobian
281
24k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
200
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
67
56k
The Curse of the Amulet
leimatthew05
2
13k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.5k
Believing is Seeing
oripsolob
1
170
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Practical Orchestrator
shlominoach
191
11k
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