Built-in method typing — boilerplate / pain-point audit — 2026-06-03
Motivation
Section titled “Motivation”次バージョンの内部最適化テーマ。このノートは、Rubyの組み込みメソッドに型を割り当てる
メカニズムにおける反復・ボイラープレート・手作業保守の痛点を棚卸しし、毎セッションで
地図を導出し直すことなく実装容易性の順でクリーンアップを進められるようにするものである。
所見のみ —— ここでは挙動変更は提案しない。false-positive disciplineルール(偽陽性
の規律)のもと、以下のクリーンアップはいずれも、畳み込み結果をビット単位で同一に保つ
コンテナのみのリファクタリングでなければならない(make verifyで検証)。
The mechanism in three layers
Section titled “The mechanism in three layers”- カタログローダー層 ——
lib/rigor/inference/builtins/*_catalog.rb(20ファイル)。それぞれ、生成されたYAMLカタログ (data/builtins/ruby_core/<topic>.yml)をMethodCatalogシングルトンと キュレートされたミューテーションブロックリストでラップしている。 - Tier-Dシングルトン畳み込み層 ——
lib/rigor/inference/method_dispatcher/*_folding.rb。純粋な標準ライブラリ シングルトン(CGI、URI、Shellwords、Math、Time、Regexp、Set、File)ごとに 1モジュールがあり、Constantのレシーバー/引数に対して推論時に呼び出しを評価する。 - ディスパッチチェーン ——
lib/rigor/inference/method_dispatcher.rbが各tierを 優先順位順に結線する。
各層には構造的な反復が蓄積している。所見は投資対効果(影響 ÷ リスク)の順で並べる。
Finding 1 — Tier-D folding modules are a copied skeleton
Section titled “Finding 1 — Tier-D folding modules are a copied skeleton”8つの*_folding.rbモジュールがほぼ同一の形を複製している:
dispatch_target?は9箇所で同じ1行:receiver.is_a?(Type::Singleton) && receiver.class_name == "X"(cgi_folding.rb:74、uri_folding.rb:51、shellwords_folding.rb:81、math_folding.rb:74、time_folding.rb:51、regexp_folding.rb:42、set_folding.rb:43、file_folding.rb:110。加えてiterator_dispatch.rb:69のClassバリアント)。- 本体の形も反復している:
module_function、try_dispatch(receiver:, method_name:, args:)、メソッド名のSetによる早期リターンガード、その後Type::Combinator.constant_of(Foo.public_send(method_name, str))をrescue StandardError → nilのフロアで包む。uri_folding.rb:43-63とcgi_folding.rb:66-90はほぼ一字一句同一である。
痛点。新たな純粋シングルトン(Base64、Digest、…)の追加は60〜120行の
ボイラープレートを書くことを意味する。rescueフロアを忘れたり、constant_ofの
引数を渡し間違えたりしやすい。
方向性。宣言テーブルで駆動する単一のSingletonFunctionFolder ——
{ "URI" => %i[encode_www_form_component …], "CGI" => {…} } —— で「単一のString
引数、public_send経由で畳み込む」ケースをカバーする。バリアントハンドラ
(CGIの要素エスケープ、ShellwordsのTupleリフト)はエントリー単位のオーバーライドとして
残す。8ファイル → 1ハーネス+データ。
Finding 2 — The dispatch chain is a hand-written || ladder
Section titled “Finding 2 — The dispatch chain is a hand-written || ladder”method_dispatcher.rb:713-720(シングルトン畳み込みチェーン)と:697-704
(dispatch_precise_tiers)は、同じreceiver:/method_name:/args:のキーワード
三つ組を15回以上反復している:
FileFolding.try_dispatch(receiver: receiver_type, method_name: method_name, args: arg_types) || ShellwordsFolding.try_dispatch(receiver: receiver_type, method_name: method_name, args: arg_types) || MathFolding.try_dispatch(receiver: ...) || … # 8 entries痛点。新たなtierとは、モジュールを書くうえにそれをラダーへ手作業で継ぎ込む ことを意味する。順序は重要(コメントに記載)だが、リストとして見ることができない。
方向性。 SINGLETON_FOLDERS = [FileFolding, ShellwordsFolding, …]を
SINGLETON_FOLDERS.lazy.filter_map { |m| m.try_dispatch(**ctx) }.firstで駆動する。
順序=配列の順序。先にFinding 3を要する。
Finding 3 — Entry-point names and signatures are not uniform
Section titled “Finding 3 — Entry-point names and signatures are not uniform”try_dispatch(cgi/uri/file/math/regexp/set/shellwords/kernel/shape)
対try_fold(constant_folding.rb:135、block_folding.rb:72)対
try_forward/try_backward(method_folding.rb)。シグネチャも分岐している:
args:のみ/+ block_type:/+ environment:, call_node:, scope:
(rbs_dispatch.rb:121は# rubocop:disable Metrics/ParameterListsを抱えている)。
痛点。これがFinding 2の汎用イテレーションを阻む根本のブロッカーである。tierの インターフェースが暗黙的なのだ。
方向性。純粋なtierをtry_dispatch(receiver:, method_name:, args:)に統一する
(エイリアス移行)。コンテキストを多く要するtierは1つのcontext:オブジェクトを取り、
ParameterListsの抑制を引退させる。
Finding 4 — Catalog loaders are boilerplate + a 3-site edit
Section titled “Finding 4 — Catalog loaders are boilerplate + a 3-site edit”各*_catalog.rbは実質的に1ステートメントである
(random_catalog.rb:24、set_catalog.rb:22、…):
XXX_CATALOG = MethodCatalog.new( path: File.expand_path("../../../../data/builtins/ruby_core/<topic>.yml", __dir__), mutating_selectors: { … })File.expand_path("../../../../data/builtins/ruby_core/…", __dir__)—— この../../../../は20ファイルにコピーされている。レイアウト変更が一斉に 全部を壊す。- 新たなカタログは3箇所の手作業編集である:
require_relativeの行 (constant_folding.rb:4-22)、CATALOG_BY_CLASSの行(:1212-1236)、そして ローダーファイルそのもの。スキャフォールドツールがこれを取り繕っているが、重複は 構造的なものである。
方向性。(a)パス解決をMethodCatalog.for_topic("set")に畳み込み、../../../../を
1箇所に隔離する。(b)ローダーに自己登録させ(MethodCatalog.register(String, "String", topic: "string"))、requireリストとCATALOG_BY_CLASSを統合する。
⚠️
mutating_selectors:のブロックリストとそのセレクタ単位のコメント (例:set_catalog.rb:38-48、random_catalog.rb:30-53)は本物の設計知識であり ——ボイラープレートではない。静的なC分類器がどの:leafエントリーを誤って帰属させ、 なぜそうなるのかを正確に記録している。一字一句そのまま保持し、コンテナだけを 薄くすること。
Finding 5 — Fixture escaping, size caps, and rescue floors re-implemented by hand
Section titled “Finding 5 — Fixture escaping, size caps, and rescue floors re-implemented by hand”rigor-type-coverage-upliftスキル自身が指摘したもの:
assert_typeのバックスラッシュ多重エスケープ —— フィクスチャが手作業で'"hello\\\\ world"'を数えている。すべての畳み込み結果アサーションでエラーが 起きやすい。- 3パラメータのハンドラ規約 ——
def h(tuple, args)がmethod_nameを黙ってargsに束縛してしまう。 - サイズ上限/
rescue → nilがフォルダーごとに再実装されている (shellwords_folding.rbのSPLIT_LIMIT = 64は意図としてはConstantFoldingのSTRING_ARRAY_LIFT_LIMITを映したものだが、別個の定数になっている)。
方向性。上限+rescueをFinding 1のハーネスに吸収する。手作業でエスケープした フィクスチャ文字列を、Rubyの値からアサート文字列を導出するヘルパーで置き換える。
Summary (ROI order)
Section titled “Summary (ROI order)”| # | Pain point | Scale | Risk |
|---|---|---|---|
| 1 | Tier-Dフォルダースケルトン(8モジュール、dispatch_target? ×9) | 大 | 低(純粋関数、十分にテスト済み) |
| 2 | 手書きの` | `ディスパッチラダー(三つ組 ×15) | |
| 3 | 不統一なエントリー名/シグネチャ | 中 | 中(広範なエイリアス移行) |
| 4 | カタログローダーのボイラープレート+3箇所編集+../../../../×20 | 中 | 低 |
| 5 | フィクスチャエスケープ/上限/rescueの再実装 | 小〜中 | 低 |
最大のテコは1+2+3をまとめて畳み込むことである: 1つのインターフェースの背後にある 純粋シングルトンフォルダー、宣言テーブル、そして配列駆動のディスパッチ。
Execution order (ease-of-implementation)
Section titled “Execution order (ease-of-implementation)”- Finding 4 —— 最小・最安全、インターフェース変更なし。パスヘルパー+自己登録。
- Finding 1 ——
SingletonFunctionFolderハーネス。まず証明としてURI / CGI / Shellwordsを移行し、続いてMath / Regexp / Set / Time / Fileを移行する。 - Finding 3 —— エントリポイントのインターフェースを統一する(2のブロックを解除)。
- Finding 2 —— エントリポイントが統一されたら配列駆動のディスパッチへ。
- Finding 5 —— フィクスチャヘルパー+上限/rescueの吸収。1と並行して機を見て行う。
各ステップはmake verify(test / lint / check / check-plugins)でゲートし、
畳み込み結果は変わらないこと。
Progress log (2026-06-03 → 2026-06-04)
Section titled “Progress log (2026-06-03 → 2026-06-04)”- Finding 4 — DONE(パスヘルパー)。単一の
DATA_ROOT配下で解決するMethodCatalog.for_topic(topic, mutating_selectors:)を追加。18個すべての インスタンスローダーを、コピーされたFile.expand_path("../../../../…", __dir__)から移行した。ブロックリストは未変更。- 自己登録のサブパート — WON’T DO(決定)。
require_relativeリストを ロード順の自己登録でCATALOG_BY_CLASSと統合するのはきれいな勝ちではない:CATALOG_BY_CLASSはサブクラスの曖昧性解消のため宣言順に走査される (Dateより前のDateTime、1つのカタログを共有するMatchData/Regexp)が、 その順序はrequireの順序と一致しない。自己登録でも明示的な順序ヒントが 依然必要となり、1つのテーブルを別のテーブルとロード順の結合に置き換えるだけに なる。明示的な3箇所編集のまま残す。 NumericCatalogの統合 — DONE。これは独自の手書きのsafe_for_folding?/method_entry/load_catalogというMethodCatalogのコピーを持つ最後の クラス単位カタログだった(汎用ローダーより前に存在していた)。NUMERIC_CATALOG = MethodCatalog.for_topic("numeric")に置き換え、CATALOG_BY_CLASSのInteger/Floatの行を再指定。バンゲートはノーオペ(畳み込み 可能な数値のバンメソッドはない)。MethodCatalogのエイリアス解決が5つの健全な 畳み込み(magnitude→abs、inspect→to_s、…)をスナップショットの移動なしに 加える。ractor対応性チェックは現在、CATALOG_BY_CLASSのディープフリーズを通じて インスタンスが共有可能であることをアサートする。
- 自己登録のサブパート — WON’T DO(決定)。
- Finding 1 — DONE。
MethodDispatcher::SingletonFoldingを抽出 (receiver?+constant_string)。9つすべてのレシーバー述語 (CGI/URI/Shellwords/Math/Time/Regexp/Set/File + iterator_dispatchのClassチェック)と、4つの文字列畳み込みのConstant[String]アンラップをこれに移行した。 完全な宣言テーブルハーネスはオーバーエンジニアリングだと結論した —— 畳み込み 本体(Mathの数値、Fileのプラットフォームゲート、Setのコンストラクタ、Timeの アリティ、Shellwordsの上限)はバラエティに富みすぎていて、ドメインロジックを 埋めずにテーブル駆動化できない。本当に同一なゲートだけを抽出した。 - Finding 2 — DONE(シングルトンチェーン)。
dispatch_stdlib_module_tiersは 現在STDLIB_MODULE_FOLDERS.eachで駆動される(8つのフォルダーはすでにtry_dispatchを共有していたので、このチェーンにはFinding 3への依存はない)。dispatch_precise_tiersのラダー(697-705)はまだtry_fold/try_dispatch/try_forward/ block_foldingを混ぜており、先にFinding 3を確かに必要とする —— 未着手。 - Finding 3 — DONE(完全なインターフェース化)。すべてのディスパッチtierが
現在、単一の不変な
CallContext(Data.define)を取り、1つのインターフェースtry_dispatch(CallContext) -> Type::t?に準拠する:- Slice A ——
CallContext値オブジェクト(9フィールド: 呼び出しの4つ組- block_type/environment/call_node/scope/self_type_override/public_only)。
.buildキーワードファクトリーが、tier単位のものを引退させる単一のMetrics/ParameterLists無効化を抱える。
- block_type/environment/call_node/scope/self_type_override/public_only)。
- Slice B1 —— precise tier(ConstantFolding
try_fold→try_dispatch、 BlockFoldingtry_fold→try_dispatch、MethodFoldingのforwardtry_forward→try_dispatch、LiteralString/Shape/Kernel + 8つのシングルトン フォルダー)。dispatch_precise_tiersはコンテキストを一度だけ構築し、単一のPRECISE_TIERSリストを駆動して、Finding 2のSTDLIB_MODULE_FOLDERSサブリストを 吸収する。 - Slice B2 —— コンテキスト重めのtier(RbsDispatch
try_dispatch—— ParameterLists無効化を除去 —— +block_param_types、MethodFoldingtry_backward、IteratorDispatchblock_param_types)。dispatchは先頭で コンテキストを一度だけ構築する。派生する2つのRBS箇所はCallContext.buildを使う。 - Slice B3 ——
interface _DispatchTier+CallContextクラスをsig/rigor/inference.rbsで宣言(Type::tで型付け)。make steep-checkは グリーン。宣言のみ —— tier単位の準拠は強制せず、エンジンの部分シグネチャの イディオムに合わせる。 - tierのエントリポイントは
method_dispatcher.rbとtierのユニットspecからのみ 呼ばれる。specは新しいcc(...)サポートヘルパー経由で移行した(Slice Bの 102呼び出し箇所)。公開のdispatch/expected_block_param_typesはキーワード シグネチャを維持する(外部の呼び出し元は影響を受けない)。 - パフォーマンス: ニュートラル。
rigor check --no-cache libのインターリーブ A/B(Finding 3前のd153403d対 後の4545dc63、4ペア) —— 前の平均7.58秒、後の 平均7.70秒(約1.6%)で、実行ごとのばらつき(6.93〜8.40秒、±15%。1ペアでは後が 速い)の十分内側。ディスパッチごとのCallContextアロケーションは針を動かさない。 - ユニット/推論specではなく全スイートで捕捉:
ShapeDispatch.dispatch_intersectionはIntersectionメンバーごとに1回try_dispatchへ再入する。そのtier内再帰は移行後も古いキーワードHashを渡し続け、NoMethodErrorを送出した。tier呼び出し元の調査はmethod_dispatcher/自身を 除外していたため見逃された ——intersection_refinementの統合フィクスチャだけが このパスを実行する。教訓: tierのエントリシグネチャを変えるときは、tier ディレクトリ自身を再帰的な自己呼び出しでgrepし、挙動ニュートラルだと主張する前に 必ず全rspec(ユニット +spec/rigor/inferenceサブセットだけでなく)を実行する こと。修正済み。5406 examples、0 failures。
- Slice A ——
- Finding 5 — NOT STARTED。フィクスチャエスケープヘルパー+上限/rescueの吸収。
© 2026 TypedDuck. Licensed under CC BY-SA 4.0.