コンテンツにスキップ

Built-in method typing — boilerplate / pain-point audit — 2026-06-03

次バージョンの内部最適化テーマ。このノートは、Rubyの組み込みメソッドに型を割り当てる メカニズムにおける反復・ボイラープレート・手作業保守の痛点を棚卸しし、毎セッションで 地図を導出し直すことなく実装容易性の順でクリーンアップを進められるようにするものである。 所見のみ —— ここでは挙動変更は提案しない。false-positive disciplineルール(偽陽性 の規律)のもと、以下のクリーンアップはいずれも、畳み込み結果をビット単位で同一に保つ コンテナのみのリファクタリングでなければならない(make verifyで検証)。

  1. カタログローダー層 —— lib/rigor/inference/builtins/*_catalog.rb (20ファイル)。それぞれ、生成されたYAMLカタログ (data/builtins/ruby_core/<topic>.yml)をMethodCatalogシングルトンと キュレートされたミューテーションブロックリストでラップしている。
  2. Tier-Dシングルトン畳み込み層 —— lib/rigor/inference/method_dispatcher/*_folding.rb。純粋な標準ライブラリ シングルトン(CGI、URI、Shellwords、Math、Time、Regexp、Set、File)ごとに 1モジュールがあり、Constantのレシーバー/引数に対して推論時に呼び出しを評価する。
  3. ディスパッチチェーン —— 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:74uri_folding.rb:51shellwords_folding.rb:81math_folding.rb:74time_folding.rb:51regexp_folding.rb:42set_folding.rb:43file_folding.rb:110。加えてiterator_dispatch.rb:69Classバリアント)。
  • 本体の形も反復している: module_functiontry_dispatch(receiver:, method_name:, args:)、メソッド名のSetによる早期リターンガード、その後 Type::Combinator.constant_of(Foo.public_send(method_name, str))rescue StandardError → nilのフロアで包む。uri_folding.rb:43-63cgi_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-704dispatch_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_foldconstant_folding.rb:135block_folding.rb:72)対 try_forward/try_backwardmethod_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:24set_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-48random_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.rbSPLIT_LIMIT = 64は意図としてはConstantFoldingの STRING_ARRAY_LIFT_LIMITを映したものだが、別個の定数になっている)。

方向性。上限+rescueをFinding 1のハーネスに吸収する。手作業でエスケープした フィクスチャ文字列を、Rubyの値からアサート文字列を導出するヘルパーで置き換える。

#Pain pointScaleRisk
1Tier-Dフォルダースケルトン(8モジュール、dispatch_target? ×9)低(純粋関数、十分にテスト済み)
2手書きの``ディスパッチラダー(三つ組 ×15)
3不統一なエントリー名/シグネチャ中(広範なエイリアス移行)
4カタログローダーのボイラープレート+3箇所編集+../../../../×20
5フィクスチャエスケープ/上限/rescueの再実装小〜中

最大のテコは1+2+3をまとめて畳み込むことである: 1つのインターフェースの背後にある 純粋シングルトンフォルダー、宣言テーブル、そして配列駆動のディスパッチ。

  1. Finding 4 —— 最小・最安全、インターフェース変更なし。パスヘルパー+自己登録。
  2. Finding 1 —— SingletonFunctionFolderハーネス。まず証明としてURI / CGI / Shellwordsを移行し、続いてMath / Regexp / Set / Time / Fileを移行する。
  3. Finding 3 —— エントリポイントのインターフェースを統一する(2のブロックを解除)。
  4. Finding 2 —— エントリポイントが統一されたら配列駆動のディスパッチへ。
  5. Finding 5 —— フィクスチャヘルパー+上限/rescueの吸収。1と並行して機を見て行う。

各ステップはmake verify(test / lint / check / check-plugins)でゲートし、 畳み込み結果は変わらないこと。

  • 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つの健全な 畳み込み(magnitudeabsinspectto_s、…)をスナップショットの移動なしに 加える。ractor対応性チェックは現在、CATALOG_BY_CLASSのディープフリーズを通じて インスタンスが共有可能であることをアサートする。
  • Finding 1 — DONEMethodDispatcher::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が 現在、単一の不変なCallContextData.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無効化を抱える。
    • Slice B1 —— precise tier(ConstantFolding try_foldtry_dispatch、 BlockFolding try_foldtry_dispatch、MethodFoldingのforward try_forwardtry_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、MethodFolding try_backward、IteratorDispatch block_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。
  • Finding 5 — NOT STARTED。フィクスチャエスケープヘルパー+上限/rescueの吸収。

© 2026 TypedDuck. Licensed under CC BY-SA 4.0.