
こんにちは、株式会社 Techouse でエンジニアをしているmachida-haruakiです。普段はクラウドハウス労務でフロントエンド・バックエンド両方の開発を担当しています。
2026年に開催されたRubyKaigi 2026へ参加してきました。本記事ではDay2に行われた @mametter さん(GitHub: @mame / X: @mametter)のセッションの内容をまとめます。タイトルは「Practical TypeProf: Lessons from Analyzing Optcarrot」です。
@mametter さんはRubyコミッターとして長年にわたりRubyの開発に関わられている方で、今回紹介される TypeProf の主要な開発者でもあります。本セッションでは、TypeProfを実際のコードベースであるOptcarrotに適用し、そこで得られた知見や改善点、そしてAI時代における型ツールの位置付けまでが幅広く語られました。型ツールの実プロジェクト適用に関心がある方、AI時代の型ツールの位置付けに興味がある方に特におすすめの内容です。
セッション概要
発表は大きく3つのパートで構成されていました。
- TypeProfとは何か(現状と直近の改善)
- Optcarrotへの適用で見えた課題と対応
- AI時代における型ツールの役割
特に印象的だったのは、3つ目のAI時代の話です。「エディタ支援を目指したツールなのに、AIエージェントへコードを書かせる時代になってエディタ自体を見ることが減ってきた」という率直な観察から議論が始まります。「AIに型を与えれば良いコードが出る」という通説を実測で再検証する内容に、型ツールへの見方を更新させられました。そこからの実験的な検証も含めて、本セッションの大きな見どころになっていました。
TypeProfとは
TypeProf は、Ruby 3.0 以降、 bundled gem として同梱されている静的型解析器です。
最大の特徴は 型注釈(アノテーション)を書かなくても型が推論できる ことです。他のRubyの型付けツール(Steep、Sorbet)では型シグネチャやコメントを書く必要がありますが、TypeProfはRubyのソースコードだけから型を推論します。
TypeProf は次のような解析機能を提供します。
- 型推論
- 型エラー・警告の検出
- RBS(型定義)の生成
- インラインRBS(
rbs-inline形式での型注釈)への対応
加えて TypeProf 自身が LSP サーバとして動作するため、これらの解析結果をエディタから利用でき、定義ジャンプ・コード補完・参照ジャンプ・メソッド名や定数名のリネームといったエディタ機能が使えます。
前回RubyKaigiからの改善
発表の冒頭では、前回の RubyKaigi 2025 のセッション からの主な改善点が紹介されました。
Better narrowing(型の絞り込みの強化)
TypeScriptなどで見られる型の絞り込みがTypeProfでも強化されました。たとえば宣言上は Integer | nil となっている変数でも、メソッド内で nil の可能性が消えるようなコードパスでは Integer として推論されます。
def example(x) # x: Integer | nil return if x.nil? # ここでは x は Integer として推論される x + 1 end
メタプログラミング対応の強化
Struct.new(:a, :b) や Data.define(:a, :b) は、渡したシンボルを元にアクセサメソッドを実行時に動的に定義するメタプログラミング的な記法です。ソースコード上には a や b のメソッド定義が現れないため、静的解析は型を推論しづらい対象でした。これに対し、TypeProf はネイティブサポートでアクセサの型を適切に推論できるようになりました。Rubyのコードベースで頻出する書き方なので、対応の効果は大きいと感じます。
コミュニティからのコントリビューション
セッションでは、コミュニティからのプルリクエストによる改善も紹介されました。
# typeprof:ignoreコメントによる警告の抑制(@x-smasato さん)- 引数転送(argument forwarding、
...)への対応(@pvcresin さん) - RBS Record Type への対応(@sinsoku さん)
特定の作者ひとりに依存せず、外部からのコントリビューションを受け入れて成長していく姿勢が伝わってきます。
Optcarrotに適用した話
ここからが本題、実際のコードベースに適用した検証結果です。
Optcarrotとは
Optcarrot はピュアRuby(C拡張に依存しない純Ruby実装)で書かれたファミコン(NES)エミュレータです。@mametter さん自身が作者で、約6000行のコードベースから成ります。RubyのJITベンチマーク(処理系の性能比較用プログラム)としてもよく使われている、Rubyコミュニティで広く知られている題材です。
@mametter さんがOptcarrotを対象に選んだ理由は、以下のような点でした。
- コードベースのサイズがちょうどよい(数千行規模)
- 自身が作者であるため、コードを誰よりも深く理解できている
- 以前から手をつけたいと思っていた
自作したプロダクトを、自作のツールで試せるということ自体、すごいなと感じました。そういう状況はたくさんのものを作り続けてきたからこそ生まれるものなので、自分もいろいろなものを作っていきたい、というモチベーションになりました。
そのまま走らせた結果
「まずはOptcarrotに対してそのままTypeProfを動かしてみよう」という試みから検証はスタートしました。
その結果、669件のエラー が報告されたそうです。
@mametter さんはこれらを 偽陽性(phantom errors) と呼んでいました。型推論としては正しいけれど、実行時には顕在化しない警告のことです。TypeProfが実用的に使われるためには、こうした偽陽性をできるかぎり減らしていく必要があります。
なお、これらを「偽陽性」と言い切れるのは、@mametter さん自身が Optcarrot の作者で、コードに実際のバグがないことを把握しているからです。
2つの戦略
偽陽性を減らすアプローチとして、発表では2つの戦略が紹介されました。
- Key points(要所)に型注釈を書く(対症療法的、実施が簡単)
- 根本原因を見つけて直す(本質的、実施が大変)
それぞれ見ていきます。
戦略1: Key pointsにRBSを書く
TypeProfは、人が書いたRBSを推論結果より優先します。
そこで、データフローの「ボトルネック」となっているメソッドにRBSを書くことで、大量の偽陽性を一気に解消できるのではないかと考えたそうです。
Optcarrotの場合、CPU#fetch というメソッドがまさに、さまざまな場所からデータが流れ込み、さまざまな場所へ流れていくボトルネックになっていました。ここに型を明示することで、エラーは 669件 → 177件(-74%) まで削減されました。
ここで課題になるのは「どこがボトルネックなのか」の見極めです。@mametter さんはOptcarrotの作者なのでそれを把握していましたが、一般には簡単ではありません。そこで試してみたのがClaude Codeです。
Claude Code に問い合わせたところ、CPU#fetch を正しく特定できたとのこと。
AIエージェントを補助的に使うアプローチが、ここでも有効な手段の1つとして示されていたのが印象的でした。
戦略2: 根本原因を直す
もう1つの戦略は、TypeProf が広めに推論せざるを得ない原因をコード側で取り除く、というアプローチです。
Optcarrotでは、メモリ確保のコードに起因して、TypeProfが @ram[n] の型を Integer | nil と推論してしまっていました。元のコードはこうなっています。
@ram = [nil] * 4 @ram[1] = 0 @ram[2] = 1 @ram[3] = 2
実行時にはOptcarrot内で @ram[n] の n は常に1〜3の範囲に収まるため、@ram[n] は 常に Integer を返します。しかし @ram[0] は nil のまま放置されているため、TypeProf は型情報として「@ram[n] は Integer | nil を返し得る」と推論します。結果として nil の可能性がコード全体に波及し、あちこちで NoMethodError の警告が積み重なる、というわけです。
修正方法は単純で、すべての要素を 0 で初期化するだけです。
@ram = [0] * 4
このたった 1行の修正 で、エラーは 669件 → 175件 まで減少しました。戦略1(RBSを3行追加して -74%)とほぼ同等の効果を、コードの正しい初期化だけで達成できた形です。
なお、こちらの根本原因はClaude Codeでは見つけられなかったとのこと。AIエージェントにも得意・不得意があり、用途に応じた使い分けが重要だと感じました。
残ったエラーへの対処
戦略1・2を適用してもまだ残るエラーには、ケースごとに使い分けて対処したそうです。発表ではいくつか具体例が示されていました。
attr_reader をループで定義しているケース
[:loglevel, :romfile, ...].each do attr_reader it end
ここで使われている it は、Ruby 3.4 で導入された暗黙のブロックパラメータです。このような書き方ではTypeProfが「どんなreaderが定義されたか」を追えません。そこでRBSを手で書いて補います。
class Optcarrot::Config def loglevel: () -> Integer def romfile: () -> String? ... end
ブロック引数が nil を含む型として推論されてしまうケース
String#scan のブロック引数のように、TypeProf的には nil の可能性を排除できないケースがあります。これは expr || raise で型を狭めます。
"foo".scan(/(\w)/) {|x,| (x || raise).upcase }
どうしても解消できない偽陽性
# typeprof:ignore で個別に抑制します。
bytes.unpack1("v") & 0x1 # typeprof:ignore
最終結果
すべての対処を合わせて、Optcarrotで 0エラー に到達するまでに必要だった変更行数は以下のとおりです。
| カテゴリ | 行数 |
|---|---|
| Rubyコードの修正 | 55 |
└ [nil] * N → [0] * N |
3 |
└ || raise の追加 |
36 |
└ # typeprof:ignore の追加 |
4 |
| └ その他 | 12 |
| RBSの追加 | 67 |
| 合計 | 122 |
6000行のコードベースに対して 122行の変更で0エラー。さらに、その作業の多くはClaude Codeに任せられたため、作業負荷は大きくなかったとのことです。
Steep・Sorbetとの比較
発表では、同じOptcarrotに対してSteepとSorbetを適用する比較実験も紹介されました。Claude Codeに型付けを任せて、それぞれのツールでエラーを0にするまでの労力と効果を測ったそうです。
| 指標 | TypeProf | Steep | Sorbet |
|---|---|---|---|
| エラー数 | 0 | 0 | 0 |
| コード変更行数 | 122 | 1429 | 1248 |
| └ メソッドシグネチャ | 67 | 1058 | 550 |
| └ ロジック変更 | 55 | 273 | 408 |
| 型チェック時間 | 1.30s | 1.96s | 0.24s |
| 実行時オーバーヘッド | 0% | 0% | 72%(チェック有)/ 17%(チェック無) |
※ メソッドシグネチャ・ロジック変更は主な内訳です。差分は「その他」に含まれます。
※ Sorbet の実行時オーバーヘッドは Optcarrot ベンチマーク上での計測値です。Sorbet は実行時に型チェックを行うかを設定可能で、「チェック有/無」はその切り替えを指します。
TypeProfは変更行数が 約1/10 に収まっています。一方で Sorbet(型チェッカ本体は C++ 実装)は TypeProf/Steep(Ruby実装)と比較して短い時間で型チェックを完了しています。Steepは型シグネチャの分量が多い分、検査の厳密さは上回ると考えられます。
なお、@mametter さんは「この比較はフェアではない」と明言されていました。理由は2つです。
- 作業労力の差として、TypeProfは @mametter さん自身で手作業+ツール改善も並行で行ったのに対し、Steep/SorbetはほとんどClaude Codeに任せた結果(仕上げが粗い)
- 検査の厳密さの差として、Steep/Sorbetはより厳密に検査する。TypeProf側は推論結果に
untypedが残ることを許容している
比較表の数字だけを見ると一見 TypeProf が圧勝しているように映ります。しかしご本人がフェアでないと明言される姿からは、ベンチマーク結果を読み解くうえで前提条件を踏まえることが大切だと改めて学びました。
AI時代における型ツール
発表の後半では、AIエージェント時代に型ツールがどう位置付けられるかという、射程の広い議論に踏み込みました。
エディタを見ない時代
TypeProfはエディタ支援を目的としたツールですが、@mametter さん自身が最近「VS Codeでコードを読み書きすることが減った」と感じているそうです。Claude Codeに書かせて、git diff で結果を確認する——そんなワークフローが増えているからです。
「エディタ支援を目指しているツールなのに、エディタを見なくなったら意味ないじゃん」
この自己言及的な問いこそが、本セッションの核心に触れる部分でした。
AIエージェントのコスト測定
そこで @mametter さんは、Claude Codeを使って言語別に同じ機能を実装させ、かかるコスト(時間・料金・安定性)を測る実験を実施しました。実験の詳細は @mametter さんがdev.toにまとめています。
主な結果は以下のとおりです。
- Ruby・Python・JavaScriptが最速かつ最安で、結果も安定していた
- Ruby with Steep は Ruby 単体よりも結果が悪かった
「AIのために型が必要」という説への再検討
近年「AIエージェントのハーネスとして型注釈が必要」という説をよく聞きます。型情報があれば、AIはより正確にコードを書けるはずだ——という仮説です。
しかし上記の実験結果を見る限り、少なくとも現時点のAIエージェントでは、型を書くことでむしろ結果が悪くなるケースすらあり得ます。
AI時代におけるTypeProfの位置付け
ではTypeProfはAI時代にどんな役割を果たすのか。発表ではこう整理されていました。
- TypeProfはRubyコードをほとんど変更しない → AIエージェントの足を引っ張らない
- 将来的には推論結果をMCPサーバ経由でAIエージェントに提供 することで補助できる可能性もある
- しかし当面は人間のためにTypeProfを改善し続ける — AIエージェントへの寄与は実践から学ぶ
「AI agentのためになるかは、しっかり測らないとわからない」
「答えは実践のなかから現れる」
この冷静なスタンスが、発表全体を通して一貫していました。流行の言説に流されず、実測で確かめ、まずは目の前の人の役に立つことを優先する——型ツール開発者としての哲学が伝わってきました。
今後の課題
発表の最後に、TypeProfの今後の課題として以下が挙げられていました。
- Editor UXの評価(まだ十分に評価できていない)
- チェックの厳密性(現状では不十分な可能性が高い)
- より大きな・多様なコードベースへの適用
- TypeProf自身の改善
- 速度・精度の向上
- より多くのDSLのサポート
- AIとの統合・実験
Optcarrotでの検証はあくまで第一歩であり、ここから先に進んでいく姿勢が明確に示されていました。
感想
本セッションの学びを3行でまとめます。
- TypeProfを実用的なコードベースに適用すると、偽陽性と根本原因の両面から攻める必要がある
- AIエージェントは型ツール開発の「補助」として十分に有効だが、万能ではない(ボトルネック特定は得意、根本原因特定は苦手)
- 「AIのために型が必要」という見方は、実測で検証してから受け止めたい示唆だった
個人的に一番の学びは、新しい道具(AIエージェント)を取り入れるときに、その効果を既存の道具(型ツール)の文脈で実測し直す姿勢 でした。AIが前提になった世界で、これまで積み上げてきたものがどう位置付くのかを、開発者自身が手を動かして確かめていく——その誠実な営みが、本セッションの根底に流れていたように感じます。
型ツールを業務で検討している方、AIエージェントと型注釈の関係に興味がある方には特におすすめのセッションでした。
セッション後、STORES CAFEでお願いしたところ、ツーショット写真も快く撮ってくださり、書籍にもサインをいただきました。本当にありがとうございました。

関連リソース
- 公式セッションページ: RubyKaigi 2026 / mametter
- @mametter さんの関連記事「Which Programming Language Is Best for Claude Code」: dev.to
- 前年セッション(TypeProfの始め方): RubyKaigi 2025 / Writing Ruby Scripts with TypeProf
- TypeProf: github.com/ruby/typeprof
- Optcarrot: github.com/mame/optcarrot
- スピーカー: @mametter さん(GitHub: @mame, X: @mametter)/ STORES, Inc.
Techouseでは、社会課題の解決に一緒に取り組むエンジニアを募集しております。 ご応募お待ちしております。