Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

マニアックなRuby 2.7新機能紹介

マニアックなRuby 2.7新機能紹介

Yusuke Endoh

January 30, 2020
Tweet

More Decks by Yusuke Endoh

Other Decks in Programming

Transcript

  1. この発表では • (基調講演らしく)Ruby開発の最新動向を紹介し、 • Ruby 2.7で導入されたりされなかったりす る新機能や非互換 を • おかしな使い方

    興味深いユースケース • あやしい仕様 言語設計における細やかな配慮 • 仕様検討の楽屋裏 新機能導入の背景や開発秘話 • を交えながら紹介していきます • 興味もったら検索→"Ruby Hack Challenge" イベント 2
  2. プログラミング言語 Ruby • 日本発のプログラミング言語(1993~) • まつもとゆきひろ氏(matz)が設計・開発している • Ruby on Railsというフレームワークが有名で、

    Webアプリの市場を(たぶんまだ) 獲得している • 処理系本体はほぼC言語で書かれている • 最近Rubyで書くところが徐々に増えている • 添付ライブラリはフルRubyのものも多数 • https://github.com/ruby/ruby
  3. Rubyの開発コミュニティ • "Ruby core team" • アクティブな開発者たちを漠然と指す謎ワード • Ruby界隈では開発者を「コミッタ」と呼ぶ •

    アクティブな開発者は10~30人程度 • 1回でもコミットしたことのある人の数:約100人 • 直近1年間で100コミット以上:約10人 • 直近1年間で10コミット以上:約20人 • 直近1年間で1コミット以上:約35人
  4. Ruby開発の日常 • 年1回、クリスマスごろリリースする • 次はRuby 2.7 • バグ報告や新機能の議論はバグトラッカでやる • https://bugs.ruby-lang.org

    • 言語の機能提案はmatzが最終決定権を持つ (『優しい独裁者』) • 議論促進のため、月1回程度、開発者会議を行う
  5. パターンマッチ • 従来からあるcase/when分岐で書くと…… 12 ary = [1, 2, 3] case

    ary[0] when 0 # マッチしない when 1 p ary[1] #=> 2 p ary[2] #=> 3 end
  6. パターンマッチの嬉しさ • 短くてわかりやすい例を示すのが難しい… • jsonを分解するのに便利?(実事例はまだない) • Railsのルーティングの記述に便利?(実事例はまだない) • 赤黒木を実装するのに便利!(実事例ではない) •

    型プロファイラを実装するのに便利!(実事例ではない) • パターンマッチは「記号処理」向きの言語機能 • Rubyで記号処理をやる人はあんまいない • (ポジティブに言えば)Rubyの適用範囲が広がる 14
  7. Rubyの配列で木を表現する 19 x y [x, y] I K [:K, :I]

    K S I K S K I K [[[:K,:I],[:S,:K]], [[:K,:S],[:I,:K]]] [[[:K,:I],[:S,:K]],[[:K,:S],[:I,:K]]]
  8. 変形ルールをプログラムで書く 20 I x x def ski(e) if e[0] ==

    :I e[1] # xの部分 else ... end end [:I, x] x
  9. 変形ルールをプログラムで書く 21 K x y x def ski(e) if e[0]

    == :I e[1] elsif e[0][0] == :K e[0][1] # xの部分 else ... end end x [[:K, x], y]
  10. 変形ルールをプログラムで書く 22 S x y z x z y z

    def ski(e) if e[0] == :I e[1] elsif e[0][0] == :K e[0][1] elsif e[0][0][0]==:S [[e[0][0][1], # x e[1]], # z [e[0][1], # y e[1]]] # z else ... end end [[[:S, x], y], z] [[x,z], [y,z]]
  11. 変形ルールをパターンマッチで書く 25 K x y x def ski(e) case e

    in [:I, x] x in [[:K, x], y] x else ... end end x [[:K, x], y]
  12. 変形ルールをパターンマッチで書く 26 S x y z x z y z

    def ski(e) case e in [:I, x] x in [[:K, x], y] x in [[[:S,x],y],z] [[x,z],[y,z]] else ... end end [[[:S, x], y], z] [[x,z], [y,z]]
  13. キーワード引数の分離 • Ruby 2.7最大の目玉非互換! • 正確にはRuby 3.0に予定されている非互換 • 前提知識:ハッシュオブジェクト •

    キーと値の対応を表すデータ構造 • 他言語ではマップ、辞書とも 29 { a: 1, b: 2 } キー 値 a 1 b 2
  14. キーワード引数の理想と現実 • 理想(Ruby 3.0予定) • 現実(Ruby 2.X) 30 def foo(a,b,c,x:42)

    ... end foo(1, 2, x: 43) メソッド 呼び出し 普通の引数に1と2を渡してね キーワード引数xは43でお願い あっ、引数cの分が足りないよ! 見直してね 1, 2, {:x=>43}の3引数を渡せ def foo(a,b,c,x:42) ... end a=1、b=2、c={:x=>43}で xは未指定な➔あとでクラッシュ foo(1, 2, x: 43)
  15. Ruby 2におけるキーワード引数 • キーワード引数は、普通の引数の一部 • 一番最後にあるハッシュオブジェクト • どちらも同じ意味 → •

    Ruby 2.0設計時はこれでいいと思っていた • しかし直感に合わないというバグ報告が次々と来た • 直感にあわせるためにad-hocな仕様変更が繰り返された • 今では完全なカオスになってしまった 31 foo(x: 43) foo({x: 43})
  16. Ruby 2.6のカオスな挙動の例 • x には何が渡るか? • 答え:何もわたらない(デフォルト式のnilになる) • 理由 32

    def foo(x=nil, **y) p x end foo({}, **{}) def foo(x=nil, **kw) p x #=> nil end foo({}, **{}) foo({}) kw={} x=デフォルト は、無と 同じ意味が自然 **{} 最後のハッシュは キーワード引数
  17. …非互換! • 意図的に混同してたコードがRuby 3で動かない 34 def foo(**opt) ... end h

    = {x: 43} foo(h) def foo(opt={}) ... end foo(x: 43) このケースは Ruby 3でも 許すことになった これは 許さない と書き直してね foo(**h)
  18. Ruby 2.7は移行支援バージョン • 基本的にはRuby 2.6と同じ意味 • しかし3.0で変わる挙動に警告を出す • www.ruby-lang.orgに移行ガイドを掲載する予定です 35

    def foo(a,b,c,opt: 42) end foo(1, 2, opt:43) #=> -:3: warning: The keyword argument is passed as the last hash parameter # -:1: warning: for `foo' defined here
  19. 委譲の新記法: (...) • 委譲:引数をすべて別メソッドに横流しすること 36 def bar(a, b, c) end

    def foo(...) bar(...) end foo(1, 2, 3) def foo(*args, &blk) bar(*args, &blk) end Ruby 2でまじめに書く場合 def foo(*args, **opts, &blk) bar(*args, **opts, &blk) end Ruby 3でまじめに書く場合
  20. トラップ:(...) はカッコが必須 • 「Rubyはカッコが省略できていいよね~」 • Ruby 2.7では行末に…があったら警告が出ます 37 def foo(...)

    bar ... end 残念!endless range と解釈されます (bar...) def foo(...) bar ... end #=> -:2: warning: ... at EOL, should be parenthesized?
  21. numbered parameter:背景 • 簡単 (?) な書き方がある • ちょっと複雑になると簡単 (?) にできなかった

    • &:to_s を魔拡張する提案が繰り返された 39 ary.map {|x| x.to_s } ary.map(&:to_s) ary.map {|x| x.to_s(16) } × ary.map {|x| JSON.parse(x) } × ary.map { _1.to_s(16) } ary.map { JSON.parse(_1) } ary.map(&:to_s << 16) ary.map(&.to_s(16)) ary.map.as_self{to_s(16)} ary.map(&(:to_s.proc >> :ord.to_proc)) ary.map(&(&:to_s >> &:ord))
  22. numbered parameter: 複数引数の罠 • _2を読み出すだけで_1の意味が変わる • こういう意味になってる 40 h =

    { 1 => 2 } h.map { p _1 } #=> [1, 2] h.map { x = _2; p _1 } #=> 1 h = { 1 => 2 } h.map {|a| p a } #=> [1, 2] h.map {|k, v| p k } #=> 1
  23. numbered parameter: 書けない場所 • 入れ子のブロックでは1回しか書けない 41 1: n.times { 2:

    _1.times { 3: _1 4: } 5: } -:3: numbered parameter is already used in -:2: outer block here こういう ややこしいのは 書いてほしくない
  24. 余談:決まるまでの長大な議論 • 2019/01: $_, @0,@1,@2, {|x|なのか {|x,|なのか • 2019/02: nobuが@1でパッチ書いてみることに

    • 2019/03: @1入りでpreview1リリースの方向 • 2019/04: eregonがブロック引数の利用例統計 • 2019/06: @, @0, it, $it, ¥it, _, 複引数必要か、{|x,| • 2019/07: it, _, %0, @, @1,@2, %1,%2, :1, :2 • 2019/09: _0 / _1,_2,_3の方向で固まる • 2019/10: _0 が消える • 開発者会議の議事録は公開されています(バグトラッカから) 43
  25. 令和対応:Date.jisx0301 • 2019/04/01 令和発表 • 2019/04/17 Ruby 2.6.3リリース(投機実行) • 2019/05/01

    令和施行 • 2019/05/20 JIS X 0301改正 45 $ ruby -rdate -e 'puts Date.new(2019, 4, 30).jisx0301' H31.04.30 $ ruby -rdate -e 'puts Date.new(2019, 5, 1).jisx0301' R01.05.01
  26. 令和対応:Unicode ¥u32FF (㋿) • 2019/04/01 令和発表 • 2019/04/?? Unicode 12.1.0

    beta • 2019/04/17 Ruby 2.6.3リリース(beta採用) • 2019/05/01 令和施行 • 2019/05/07 Unicode 12.1.0リリース 46 $ ruby -e 'puts "㍻".unicode_normalize(:nfkd)' 平成 $ ruby -e 'puts "㋿".unicode_normalize(:nfkd)' 令和
  27. pipeline operatorの本来の目的 • 関数名を処理順に書けるようにすること • F# • Elixir • 非オブジェクト指向言語でメソッドチェーンっぽく

    書くためのハックだった • Rubyではメソッド呼び出しの別記法とするのは自然 49 x |> foo 1 |> bar 2 bar 2 (foo 1 x) = bar(foo(x, 1), 2) x |> foo(1) |> bar(2) =
  28. Rubyが狙っていたこと • Range#eachのカッコを省略したかった • メソッドチェーンに コメントを書きたかった • プログラミング言語は 見た目が9割! 51

    x # fooをやる |> foo 1 # barをやる |> bar 2 1..10 |> each {|x| ... } x # fooをやる .foo 1 # barをやる .bar 2 Ruby 2.7では これが書ける
  29. LISP-1とLISP-2 • Python (LISP-1) • 括弧無→メソッド取出 • 括弧付→メソッド呼出 • 一貫している

    •Ruby (LISP-2) • 括弧ありでも無しでも メソッド呼び出し • メソッド取出が面倒 • 取出してから呼出も面倒 54 "str".upper() #=> "STR" "str".upper #=> <built-in method upper of str object at…> "str".upcase #=> "STR" "str".method(:upcase) #=> #<Method: String#upcase> "str".:upcase.call "str".:upcase #=> #<Method: String#upcase>
  30. 消された理由 • 「関数型プログラミング?」の用途には不完全 • ↓のケースは .: で解決しない • 今後も &:to_s

    を魔拡張しつづけるのか……? • Rubyの関数型プログラミングのgrand planを (誰かが)考えてから再挑戦することに 56 [json1, json2].map {|x| JSON.parse(x, symbolize_names: true) }
  31. 2.7は他にも新機能や改善 や非互換 がたくさん • IRBの刷新 • $SAFE消滅 • filter_map •

    Enumerable#tally • GC.compact • Time#floor, #ceil 57 • beginless range • self.private_method • Array#intersection • Comparable#clamp with range • CESU-8 • Enumerator.produce • Enumerator::Lazy#eager • Enumerator::Yielder#to_proc • Fiber#raise • FrozenError#receiver • IO#set_encoding_by_bom • Integer#[] with range • Method#inspect • Module#const_source_location • 一部のto_sがfrozen • ObjectSpace::WeakMap#[]= • Regexp#match?(nil) • RubyVM.resolve_feature_pathの移動 • UnboundMethod#bind_call • Bundler更新 • CGI.escapeHTML高速化 • CSV更新 • Net::FTP改良 • Net::IMAP改良 • open-uri改良 • OptionParser did_you_mean • Racc 更新 • REXML更新 • RSS更新 • RubyGems更新 • StringScanner更新 • 一部の標準ライブラリがgem化 • ほか クックパッド開発者ブログで網羅解説予定 w/ ko1
  32. まとめ • Ruby 2.7にご期待ください • 数多くの新機能や改良 • Ruby 3を見据えた準備 •

    意外といろいろ考えてやってます • あなたもRuby開発に参加できます • メーリングリストやバグトラッカをウォッチ • もしくは、検索:"Ruby Hack Challenge" 59