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
型プロファイラ:抽象解釈に基づくRuby 3の静的解析
Search
Yusuke Endoh
April 20, 2020
Programming
0
17
型プロファイラ:抽象解釈に基づくRuby 3の静的解析
2020-04-17 型システム祭りオンライン
https://opt.connpass.com/event/169724/
Yusuke Endoh
April 20, 2020
Tweet
Share
More Decks by Yusuke Endoh
See All by Yusuke Endoh
12年前の『型システム入門』翻訳の思い出話
mame
11
1.5k
Good first issues of TypeProf
mame
4
5.2k
Revisiting TypeProf - IDE support as a primary feature
mame
1
2.1k
error_highlight: User-friendly Error Diagnostics
mame
0
13
TRICK 2022 Results
mame
0
9
クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料
mame
0
15
Enjoy Ruby Programming in IDE and TypeProf
mame
0
17
TypeProf for IDE: Enrich Development Experience without Annotations
mame
0
13
Ruby 3の型解析に向けた計画
mame
0
17
Other Decks in Programming
See All in Programming
マイグレーションコード自作して File-Based Routing に自動移行!! ~250 ページの歴史的経緯を添えて~
cut0
1
240
Swiftで高速フーリエ変換してオーディオビジュアライザーを作る / iOSDC Japan 2024 Day1 Track D
kyome22
2
460
Scala におけるコンパイラエラーとの付き合い方
chencmd
1
150
Rubyとクリエイティブコーディングの輪の広がり / The Growing Circle of Ruby and Creative Coding
chobishiba
1
220
状態管理ライブラリZustandの導入から運用まで
k1tikurisu
2
240
ゲームボーイアドバンスでSwiftを動かそう
k_koheyi
0
510
『ドメイン駆動設計をはじめよう』中核の業務領域
masuda220
PRO
5
840
iOSの隠されたAPIを解明し、開発効率を向上させる方法/iOSDC24
noppefoxwolf
2
120
Playwrightから始めるVisual Regression Testingのススメ by とっと
totto2727
2
1.8k
私のEbitengineの第一歩
qt_luigi
0
410
Some more adventure of Happy Eyeballs
coe401_
2
150
GoのIteratorに詳しくなってしまう
inatonix
1
170
Featured
See All Featured
How to train your dragon (web standard)
notwaldorf
85
5.6k
What's in a price? How to price your products and services
michaelherold
241
11k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
131
32k
GraphQLとの向き合い方2022年版
quramy
43
13k
Web development in the modern age
philhawksworth
204
10k
Large-scale JavaScript Application Architecture
addyosmani
508
110k
The World Runs on Bad Software
bkeepers
PRO
64
11k
The Power of CSS Pseudo Elements
geoffreycrofte
71
5.2k
Thoughts on Productivity
jonyablonski
65
4.2k
The Brand Is Dead. Long Live the Brand.
mthomps
53
37k
Building a Modern Day E-commerce SEO Strategy
aleyda
35
6.8k
Designing with Data
zakiwarfel
98
5k
Transcript
型プロファイラ: 抽象解釈に基づくRuby 3の静的解析 遠藤 侑介(@mametter) 2020/04/17 型システム祭り 1
Yusuke Endoh (@mametter) • クックパッドで Ruby の開発者やってます • フルタイムRubyコミッタ •
本日は完全なaway 2
Yusuke Endoh (@mametter) • クックパッドで Ruby の開発者やってます • フルタイムRubyコミッタ •
本日は完全なaway • TAPL翻訳しました!!! (マウンティング) 2
アジェンダ • 背景:Ruby 3の静的型解析の計画 • 型プロファイラ:Ruby 3の型推論器 • デモ •
今後の話(与太話) 3
アジェンダ • ➔背景:Ruby 3の静的型解析の計画 • Rubyとは • 静的型解析の問題設定とアプローチ • 型プロファイラ:Ruby
3の型推論器 • デモ • 今後の話(与太話) 4
Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い •
並列実行できない • 静的検証がない 5
Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い •
並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は……
Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い •
並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は…… → JITで3倍速くする
Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い •
並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は…… → JITで3倍速くする → 並行並列機能を入れる
Rubyとは • Webアプリの開発でよく使われている言語 • ユーザ:クックパッド、GitHub、AirBnBなど • 現行の欠点は… • 遅い •
並列実行できない • 静的検証がない 5 ➔Ruby 3の目標は…… → JITで3倍速くする → 並行並列機能を入れる → 静的型解析を入れる
Ruby 3の静的型解析の問題設定 • まつもとゆきひろ(Ruby言語設計者)曰く 6
Ruby 3の静的型解析の問題設定 • まつもとゆきひろ(Ruby言語設計者)曰く "I Hate Type Annotation" 6
Ruby 3の静的型解析の問題設定 • 実行前にバグの可能性を見つけられることは価値 • ただし、Rubyの開発体験はなるべく維持したい 7
Ruby 3の静的型解析の問題設定 • 実行前にバグの可能性を見つけられることは価値 • ただし、Rubyの開発体験はなるべく維持したい • Rubyの開発体験:簡潔さと柔軟性 • 簡潔な記述性は人類にはとても重要
• 動的性や柔軟性は現実世界の運用に役立っている 7
Ruby 3の静的型解析の問題設定 • 実行前にバグの可能性を見つけられることは価値 • ただし、Rubyの開発体験はなるべく維持したい • Rubyの開発体験:簡潔さと柔軟性 • 簡潔な記述性は人類にはとても重要
• 動的性や柔軟性は現実世界の運用に役立っている ➔解析の健全性を妥協しバランスのよい点を探す 7
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc(n) n + 1
end コード
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer
① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer
① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler)
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer
① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler) ③ 型検査 (Steep, Sorbet, …)
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer
① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler) ③ 型検査 (Steep, Sorbet, …) ①+③=大体TypeScript
Ruby 3の静的型解析のアプローチ Ruby 3は型記述言語・型検査・型推論を提供したい 8 def inc: (Integer) -> Integer
① 型記述言語 (RBS; ruby-signature) def inc(n) n + 1 end コード ② 型推論 (ruby-type-profiler) ③ 型検査 (Steep, Sorbet, …) ①+③=大体TypeScript ②=型注釈なし解析を 試みる無謀な挑戦
アジェンダ • Ruby 3の静的型解析の計画 • ➔型プロファイラ:Ruby 3の型推論器 • 解析アルゴリズムの概要 •
普通の型システムとの違い • デモ • 今後の話(与太話) 9
Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def
foo(n) n.to_s end foo(42)
Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def
foo(n) n.to_s end foo(42) Integer
Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def
foo(n) n.to_s end foo(42) Integer String
Ruby 3の型推論 • 型プロファイラ:型レベルのRubyインタプリタ • メソッドが受け取った型・返した型を集めて表示する • 型(のようなもの)を抽象値ドメインとする抽象解釈 10 def
foo(n) n.to_s end foo(42) Integer String def foo: (Integer) -> String
型プロファイラ:分岐の扱い • 分岐:両方の節を実行して、Union型で合流する • ループ:型を拡大しながら収束するまで繰り返す 11 if rand < 0.5
x = 42 #=> Integer else x = "str" #=> String end p(x) #=> Integer|String
話題はたくさんあるけど略 原理的な話 • 関数呼び出し、再帰呼び出し • クロージャ • 型を変更する変数代入 • コンテナ型
• 型を変更するコンテナ破壊 • flow-sensitiveな解析と エスケープ解析の援用 • any型の扱い • コールスタックなし大域脱出 • 値レベルの言語機能 実用上の話 • 解析速度と精度のトレードオフ • 配列はタプルかつシーケンス • 解析結果の説明性 • 解析未到達コードの解析 • バイトコード解析の制限 • 山ほどあるRuby言語機能 • 複雑すぎるRubyの引数 • オブジェクト指向の扱い • 組み込みライブラリの型 • 型レベル言語機能プラグイン 12 ※まだ未実装のネタも含みます
普通の型システムとの違い • 普通の型システムはメソッド単位で解析する • 仮引数の型はどうする? • 手書きする(TypeScript他) • 使われ方を見て決める(ML他) 13
普通の型システムとの違い • 普通の型システムはメソッド単位で解析する • 仮引数の型はどうする? • 手書きする(TypeScript他) • 我々の目的には合わない •
使われ方を見て決める(ML他) 13
普通の型システムとの違い • 普通の型システムはメソッド単位で解析する • 仮引数の型はどうする? • 手書きする(TypeScript他) • 我々の目的には合わない •
使われ方を見て決める(ML他) • 右のような例で難しい • 構造的部分型? 13 class A def foo = 42 end class B def foo = "str" end def f(n) n.foo #=> 何? end
型プロファイラの主要な問題点 • 解析が遅い(速度と精度のトレードオフ) • 解析の起点(テスト)が必要 • 未使用関数は引数anyとしか言えない • 誤推定は生じる •
解析結果の理解が難しい • 開発体験が未知 • いろいろ工夫を考えながらやってます 14
アジェンダ • Ruby 3の静的型解析の計画 • 型プロファイラ:Ruby 3の型推論器 • ➔デモ •
3Dトレーサ • 最長共通列ライブラリ • 型プロファイラ自身を型プロファイル • 今後の話(与太話) 15
いい感じのデモ:ao.rb • 3Dレイトレーサ • 300行のトイプログラム • Syoyo Fujita, Hideki Miura作
• https://code.google.com/ archive/p/aobench/ • 若干改変あり 16
Demo: ao.rb 17 class Vec @x : Float @y :
Float @z : Float initialize : (Float, Float, Float) -> Float x : () -> Float x= : (Float) -> Float ... vadd : (Vec) -> Vec vsub : (Vec) -> Vec vcross : (Vec) -> Vec vdot : (Vec) -> Float vlength : () -> Float vnormalize : () -> Vec
Demo: ao.rb 17 class Vec @x : Float @y :
Float @z : Float initialize : (Float, Float, Float) -> Float x : () -> Float x= : (Float) -> Float ... vadd : (Vec) -> Vec vsub : (Vec) -> Vec vcross : (Vec) -> Vec vdot : (Vec) -> Float vlength : () -> Float vnormalize : () -> Vec 3Dベクトルのクラス
Demo: ao.rb 17 class Vec @x : Float @y :
Float @z : Float initialize : (Float, Float, Float) -> Float x : () -> Float x= : (Float) -> Float ... vadd : (Vec) -> Vec vsub : (Vec) -> Vec vcross : (Vec) -> Vec vdot : (Vec) -> Float vlength : () -> Float vnormalize : () -> Vec 3Dベクトルのクラス 座標
Demo: ao.rb 17 class Vec @x : Float @y :
Float @z : Float initialize : (Float, Float, Float) -> Float x : () -> Float x= : (Float) -> Float ... vadd : (Vec) -> Vec vsub : (Vec) -> Vec vcross : (Vec) -> Vec vdot : (Vec) -> Float vlength : () -> Float vnormalize : () -> Vec 3Dベクトルのクラス 座標 attr_accessor
Demo: ao.rb 17 class Vec @x : Float @y :
Float @z : Float initialize : (Float, Float, Float) -> Float x : () -> Float x= : (Float) -> Float ... vadd : (Vec) -> Vec vsub : (Vec) -> Vec vcross : (Vec) -> Vec vdot : (Vec) -> Float vlength : () -> Float vnormalize : () -> Vec 3Dベクトルのクラス ベクトル演算たち 座標 attr_accessor
Demo: ao.rb 18 class Scene @spheres : [Sphere, Sphere, Sphere]
@plane : Plane initialize : () -> Plane ambient_occlusion : (Isect) -> Vec render : (Integer, Integer, Integer) -> Integer end class Sphere @center : Vec @radius : Float initialize : (Vec, Float) -> Float intersect : (Ray, Isect) -> (NilClass | Vec) end
Demo: ao.rb 18 class Scene @spheres : [Sphere, Sphere, Sphere]
@plane : Plane initialize : () -> Plane ambient_occlusion : (Isect) -> Vec render : (Integer, Integer, Integer) -> Integer end class Sphere @center : Vec @radius : Float initialize : (Vec, Float) -> Float intersect : (Ray, Isect) -> (NilClass | Vec) end 3つの球
Demo: ao.rb 18 class Scene @spheres : [Sphere, Sphere, Sphere]
@plane : Plane initialize : () -> Plane ambient_occlusion : (Isect) -> Vec render : (Integer, Integer, Integer) -> Integer end class Sphere @center : Vec @radius : Float initialize : (Vec, Float) -> Float intersect : (Ray, Isect) -> (NilClass | Vec) end 3つの球 球は中心と半径
Demo: ao.rb 19 class Ray @org : Vec @dir :
Vec initialize : (Vec, Vec) -> Vec org : () -> Vec dir : () -> Vec end class Isect @t : Float @hit : FalseClass | TrueClass @pl : Vec @n : Vec initialize : () -> Vec
Demo: ao.rb 19 class Ray @org : Vec @dir :
Vec initialize : (Vec, Vec) -> Vec org : () -> Vec dir : () -> Vec end class Isect @t : Float @hit : FalseClass | TrueClass @pl : Vec @n : Vec initialize : () -> Vec 光線は起点と方向
Demo: ao.rb 19 class Ray @org : Vec @dir :
Vec initialize : (Vec, Vec) -> Vec org : () -> Vec dir : () -> Vec end class Isect @t : Float @hit : FalseClass | TrueClass @pl : Vec @n : Vec initialize : () -> Vec 光線は起点と方向 交点の判定・計算
Demo: ao.rb 19 class Ray @org : Vec @dir :
Vec initialize : (Vec, Vec) -> Vec org : () -> Vec dir : () -> Vec end class Isect @t : Float @hit : FalseClass | TrueClass @pl : Vec @n : Vec initialize : () -> Vec 光線は起点と方向 交点の判定・計算 交わるか否か boolean相当
Demo: ao.rb • いい感じでは? 20
事例:diff-lcs • 最長共通部分列ライブラリ • ダウンロード数4位の 大人気ライブラリ→ • 簡単なテスト↓を起点に解析 21 https://bestgems.org/
require_relative "diff-lcs/lib/diff/lcs" class T; end Diff::LCS.diff([T.new]+[T.new], [T.new]+[T.new]) {}
diff-lcs解析結果( そこそこ いい感じの例) 22 class Diff::LCS::Change include Comparable @element :
NilClass | T | any @position : Integer | any @action : :+ | :- | any self.valid_action? : (:! | :+ | :- | :< | :== | :> | any ) -> (FalseClass | TrueClass) action : () -> (String | any ) position : () -> (Integer | any ) element : () -> (NilClass | T | any ) initialize : (String | any , Integer | any , NilClass | T | any ) -> NilClass to_a : () -> ([String | any , Integer | any , NilClass | T | any ]) unchanged? : () -> (FalseClass | TrueClass | any ) end 誤推定っぽいのは薄くしてます
diff-lcs解析結果( そこそこ いい感じの例) 22 class Diff::LCS::Change include Comparable @element :
NilClass | T | any @position : Integer | any @action : :+ | :- | any self.valid_action? : (:! | :+ | :- | :< | :== | :> | any ) -> (FalseClass | TrueClass) action : () -> (String | any ) position : () -> (Integer | any ) element : () -> (NilClass | T | any ) initialize : (String | any , Integer | any , NilClass | T | any ) -> NilClass to_a : () -> ([String | any , Integer | any , NilClass | T | any ]) unchanged? : () -> (FalseClass | TrueClass | any ) end 列の中の要素 誤推定っぽいのは薄くしてます
diff-lcs解析結果( そこそこ いい感じの例) 22 class Diff::LCS::Change include Comparable @element :
NilClass | T | any @position : Integer | any @action : :+ | :- | any self.valid_action? : (:! | :+ | :- | :< | :== | :> | any ) -> (FalseClass | TrueClass) action : () -> (String | any ) position : () -> (Integer | any ) element : () -> (NilClass | T | any ) initialize : (String | any , Integer | any , NilClass | T | any ) -> NilClass to_a : () -> ([String | any , Integer | any , NilClass | T | any ]) unchanged? : () -> (FalseClass | TrueClass | any ) end 列の中の要素 追加削除の位置 誤推定っぽいのは薄くしてます
diff-lcs解析結果( そこそこ いい感じの例) 22 class Diff::LCS::Change include Comparable @element :
NilClass | T | any @position : Integer | any @action : :+ | :- | any self.valid_action? : (:! | :+ | :- | :< | :== | :> | any ) -> (FalseClass | TrueClass) action : () -> (String | any ) position : () -> (Integer | any ) element : () -> (NilClass | T | any ) initialize : (String | any , Integer | any , NilClass | T | any ) -> NilClass to_a : () -> ([String | any , Integer | any , NilClass | T | any ]) unchanged? : () -> (FalseClass | TrueClass | any ) end 列の中の要素 追加削除の位置 追加 or 削除 ※元コードはStringでしたがデモのためSymbolに書き換えた 誤推定っぽいのは薄くしてます
diff-lcs解析結果(難しい例) • 引数に依存して返り値の型が変わるメソッド • diff(ary, ary, DiffCallbacks) ➔ Array[Array[Diff::LCS::Change]] •
diff(ary, ary, SDiffCallbacks)➔ Array[Diff::LCS::ContextChange] • オーバーロードのRBSは手書きしてください 23 module Diff::LCS self.diff : (Array[T] | Diff::LCS, Array[T] | any, ?NilClass) -> (Array[Array[Diff::LCS::Change | NilClass | any ] | Diff::LCS::Change | Diff::LCS::ContextChange | NilClass | any ] | any ) end
diff-lcsで出た警告の例 • flow-sensitiveな解析が必要 ※実際にはもっといっぱい出てます 個別に原因究明して、修正や改善検討をする…… 24 if callbacks.respond_to?(:finished_a) and …
… callbacks.finished_a(event) #=>「NilClass#finished_aを呼ぶかも」警告が出る … else
事例:type-profiler • 型プロファイラのコード • Rubyで書かれている(5000行くらい)ので • 型プロファイルできる 25
type-profiler解析結果(いい感じの例) 26 class TP::Type include TP::Utils::StructuralEquality self.any : () ->
TP::Type::Any self.bool : () -> TP::Type::Union self.nil : () -> TP::Type::Instance self.optional : (TP::Type | TP::Type::Any | TP::Type::Array | … | any) -> (TP::Type | TP::Type::Any | TP::Type::Array | … | any) self.guess_literal_type : (any) -> (TP::Type::Any | TP::Type::Array | … | TP::Type::Symbol) … end
type-profilerいろいろ問題点(抜粋) • 再帰データ構造の扱いが微妙 • 巨大なUnionが出てきてつらい • 継承関係を利用してまとめる? • 型のエイリアスをうまく作る? •
Object#method経由の呼び出しが追えない • 作り込みが足らない ※実際にはもっといっぱい(略) 27 (TP::Type | TP::Type::Any | TP::Type::Array | … | any)
FAQ • X | any は any と同じでは? • おっしゃるとおり
• でも敵術のプロトタイプ生成には便利なので あえて潰さずに残している 28
アジェンダ • Ruby 3の静的型解析の計画 • 型プロファイラ:Ruby 3の型推論器 • デモ •
➔今後の話(与太話) 29
polyglot • 2つ(以上)の言語で解釈可能なプログラム • 例:RubyとJavaScriptのpolyglot • コツ:片方の言語だけで解釈されるように頭を使う 30 if (0)
print("Hello Ruby") [ここはRuby] else console.log("Hello JS") [ここはJS] // end
型付き言語のプログラムはpolyglot • インタプリタが動的な意味で解釈できる • さらに、型システムが静的な意味で解釈できる 31
型付き言語のプログラムはpolyglot • インタプリタが動的な意味で解釈できる • さらに、型システムが静的な意味で解釈できる • 型注釈はチート(polyglotの観点では) • 型システムだけに指示を与えられるずるい記述 31
型プロファイラの開発体験? • ずるくないpolyglot • 普通の実行に加え、型レベル実行を意識して書く • 別言語ではないので、そこまで辛くはない(はず) 32
型プロファイラの開発体験? • ずるくないpolyglot • 普通の実行に加え、型レベル実行を意識して書く • 別言語ではないので、そこまで辛くはない(はず) • それでもメリットはある(はず) •
型注釈なしの記述は疑いなくシンプル 32 def foo(n: Integer | String) : Integer | String p n end foo(1) foo("str") def foo(n) p n end foo(1) foo("str") vs.
型プロファイラの開発体験? • ずるくないpolyglot • 普通の実行に加え、型レベル実行を意識して書く • 別言語ではないので、そこまで辛くはない(はず) • それでもメリットはある(はず) •
型注釈なしの記述は疑いなくシンプル • 型プロファイラが解析できる≒素直な良いコード? 32 def foo(n: Integer | String) : Integer | String p n end foo(1) foo("str") def foo(n) p n end foo(1) foo("str") vs.
進捗と今後 • 現状:やっとスタート地点 • 解析器の基本設計ができた • Rubyのおおよその言語機能がサポートできてきた • 組み込みクラスの知識をRBSから取り込んだ •
今後:実験と改善を繰り返す • バグの洗い出しと修正 • プログラミング体験の設計と不足機能の実装 • 診断機能、差分更新機能 • Railsアプリ解析用のドライバ開発 • など 33
謝辞 • 三浦さん(mrubyでの先行研究事例) • soutaroさん(RBS、その他議論) • uenoBさんとesumiiさん(アドバイス多数) • matz、akr、ko1などのRuby開発者たち •
Sorbetの人たち(Stripe, Shopifyなど) • Jeff Foster(10年の既存研究、別方法の型推論) 34
まとめ • Ruby 3は型記述言語・型推論・型検査を 提供したい • 型推論担当の型プロファイラをやってます • (寛容な心で)手伝ってくれる人たのむ! •
https://github.com/mame/ruby-type-profiler 35
説明しなかったこと • オーバーロードの推定は諦めた • 爆発する • オーバーロードするときは基本的に手書きして • 再帰呼び出しはいい感じにできる •
でも再帰的なデータ構造のハンドリングは微妙 • カスタムメソッド • 型プロファイラプラグイン • インスタンス変数の配列の破壊 • を説明するには、まずコンテナ型がメソッドを跨がらな いことを説明しないと…… 36