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
26
型プロファイラ:抽象解釈に基づく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
An Invitation to TRICK: How to write weird Ruby programs
mame
1
700
TypeProf進捗
mame
0
19
12年前の『型システム入門』翻訳の思い出話
mame
14
1.9k
Good first issues of TypeProf
mame
4
6.4k
Revisiting TypeProf - IDE support as a primary feature
mame
1
2.3k
error_highlight: User-friendly Error Diagnostics
mame
0
21
TRICK 2022 Results
mame
0
24
クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料
mame
0
29
Enjoy Ruby Programming in IDE and TypeProf
mame
0
28
Other Decks in Programming
See All in Programming
Haze - Real time background blurring
chrisbanes
1
510
Jakarta EE meets AI
ivargrimstad
0
240
これが俺の”自分戦略” プロセスを楽しんでいこう! - Developers CAREER Boost 2024
niftycorp
PRO
0
190
フロントエンドのディレクトリ構成どうしてる? Feature-Sliced Design 導入体験談
osakatechlab
8
4.1k
テスト自動化失敗から再挑戦しチームにオーナーシップを委譲した話/STAC2024 macho
ma_cho29
1
1.3k
Keeping it Ruby: Why Your Product Needs a Ruby SDK - RubyWorld 2024
envek
0
180
今年一番支援させていただいたのは認証系サービスでした
satoshi256kbyte
1
250
nekko cloudにおけるProxmox VE利用事例
irumaru
3
430
Monixと常駐プログラムの勘どころ / Scalaわいわい勉強会 #4
stoneream
0
270
ドメインイベント増えすぎ問題
h0r15h0
2
300
useSyncExternalStoreを使いまくる
ssssota
6
1k
テストコードのガイドライン 〜作成から運用まで〜
riku929hr
3
350
Featured
See All Featured
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
127
18k
Building Adaptive Systems
keathley
38
2.3k
How To Stay Up To Date on Web Technology
chriscoyier
789
250k
Put a Button on it: Removing Barriers to Going Fast.
kastner
59
3.6k
The Pragmatic Product Professional
lauravandoore
32
6.3k
Large-scale JavaScript Application Architecture
addyosmani
510
110k
Being A Developer After 40
akosma
87
590k
Fashionably flexible responsive web design (full day workshop)
malarkey
405
66k
Why Our Code Smells
bkeepers
PRO
335
57k
Navigating Team Friction
lara
183
15k
Why You Should Never Use an ORM
jnunemaker
PRO
54
9.1k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
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