Slide 1

Slide 1 text

Ruby 3のキーワード引数 について考える The Ruby Team 2018/09/15 (Sat.) 大江戸Ruby会議7 1

Slide 2

Slide 2 text

今日の議題 • Ruby 2のキーワード引数の問題 • Ruby 3に向けた改善の提案 2

Slide 3

Slide 3 text

『キーワード拡張』 • メソッドに新たにキーワード引数を持たせること • 『キーワード拡張』は常に安全か? – もともとあった呼び出しは元の通りに動いてほしい def foo(...) end foo(...) def foo(..., extension: false) end foo(...) foo(..., extension: true) 3

Slide 4

Slide 4 text

キーワード拡張は安全でない def foo(*args) p args end foo(1, 2, 3) #=> [1, 2, 3] foo(k: 42) #=> [{:k=>42}] 4

Slide 5

Slide 5 text

キーワード拡張は安全でない def foo(*args, extension: false) p args end foo(1, 2, 3) #=> [1, 2, 3] foo(k: 42) #=> unknown key: k 5

Slide 6

Slide 6 text

問題:キーワード拡張が安全でない • rest引数をとるメソッドのキーワード拡張は危険 – 既存コードが動かなくなるリスクがある – optional引数も同様にダメ • バグ報告が多数来ている – #8316,#11967,#12104,#12717,#12821,#13336,#13647,#14130 • 実際にAPI拡張で困っている – Thread.new(stack_size: 100000)とか – Struct.new(keyword_init: true)とか 6

Slide 7

Slide 7 text

問題の原因 • キーワードとハッシュを自動変換していること def foo(h) p h #=>{:k=>42} end foo(k: 42) def foo(k: 1) p k #=> 42 end foo({ k: 42 }) キーワード➔ハッシュ ハッシュ➔キーワード 7

Slide 8

Slide 8 text

Ruby 3での解決提案 • キーワードハッシュの変換をやめる #14183 – キーワードかハッシュか、明示してください 注意:Ruby 3の決定事項ではない def foo(h) p h #=>{:k=>42} end foo(k: 42) def foo(k: 1) p k #=> 42 end foo({ k: 42 }) def foo(h) p h[:k] #=> 42 end foo({ k: 42 }) def foo(h) p h #=>{:k=>42} end foo({ k: 42 }) def foo(**h) p h #=>{:k=>42} end foo(k: 42) def foo(k: 1) p k #=> 42 end foo(**{ k: 42 }) 8

Slide 9

Slide 9 text

互換性の問題 • 修正箇所は少なくないが、修正は簡単 – だいたいは、**をつけるだけ • 一部、むずかしいケースがある 9

Slide 10

Slide 10 text

互換性問題1:既存API • キーワードとハッシュのどちらも許すAPI • こういうAPIをRuby 3で定義するには? – 修正方法:両方受け取ってマージしてください erb.result_with_hash(k: 1) erb.result_with_hash(hash) def result_with_hash(h1={}, *h2) h = h1.merge(h2) ... end 10

Slide 11

Slide 11 text

互換性問題2:委譲 • 引数を丸投げするコード • こういうコードをRuby 3で書くには? – 修正方法:**kw も委譲してください def forward(*args, &blk) target(*args, &blk) end def forward(*args, **kw, &blk) target(*args, **kw, &blk) end 11

Slide 12

Slide 12 text

議題 • 議題0:問題と提案に対するお気持ちは? • 議題1:foo(:key=>1)はキーワード? • 議題1':foo("str"=>1, key:2)は? • 議題2:既存APIのための記法? • 議題3:委譲のための記法? 12

Slide 13

Slide 13 text

議題1 • Ruby 3で、以下はキーワード?ハッシュ? – 案1:キーワードにする?(Ruby 2と互換) – 案2:=>だったら常にハッシュにする?(非互換) – 案3:文法エラー?(=>は{}を書かないとダメ) foo(:key=>1) 13

Slide 14

Slide 14 text

議題1' • Ruby 3で、以下はキーワード?ハッシュ? – 案1:キーの種類で自動分割する? • foo({"str"=>1}, key:2) – 案2:非Symbolがあったら全部ハッシュ? • foo({"str"=>1, key:2}) – 案3:文法エラーにする?(混ぜるな危険) • 参考:こういうAPIは実在する(Kernel#spawn) foo("str"=>1, key:2) 14

Slide 15

Slide 15 text

議題1'' • Ruby 3で、以下はキーワード?ハッシュ? – 案1:キーの種類で自動分割する? • foo({"s"=>2,"ss"=>4}, k:1, kk:3) – 案2:非Symbolがあったら全部ハッシュ? • foo({k:1, "s"=>2, kk:3, "ss"=>4}) – 案3:文法エラーにする?(混ぜるな危険) foo(k:1, "s"=>2, kk:3, "ss"=>4) 15

Slide 16

Slide 16 text

議題2 • Ruby 2のように動くメソッドを簡単に定義したい – 案1:諦めてもらう • 手動マージしてください – 案2:互換定義用のAPIを提供する def result_with_hash(***h); end ruby2 def result_with_hash(h); end define_ruby2_method(:result_with_hash){} 16

Slide 17

Slide 17 text

議題3 • Ruby 2.6 と Ruby 3 の両方でうごく委譲は? – 委譲専用の記法を導入する? #3447 def forward(...) target(...) end 17

Slide 18

Slide 18 text

議題3' • Ruby 2.6 と Ruby 3 の両方でうごく委譲は? – ↓は Ruby2.6で若干問題がある – forward() が target(**{}) を呼ぶ • target()とtarget(**{})は微妙に意味が違う • 実用上は問題ない? def forward(*args, **kw, &blk) target(*args, **kw, &blk) end 18

Slide 19

Slide 19 text

議題3'' • Ruby 2.5 は **{} がバグっている #15052 • 2.6で、どう直す? def foo(opt=42) p opt end foo(**{}) #=> 42 h={} foo(**h) #=> {} 19

Slide 20

Slide 20 text

議題3''' • Ruby 2.6で、以下の意味は? foo(**{}) def foo(opt=42,**kw) p [opt, kw] end foo({}, **{}) #foo({})と同じ #=> [42, {}] def foo(opt=42) p opt end foo(**{}) #=> {} 案1:「無」とみなす? 案2:空ハッシュを渡す? 案3:いい感じに(難しい) 20