【一人 Ruby Advent Calendar 2017】ginza.rb で Ruby 2.5 の話を聞いてきた【21日目】

一人 Ruby Advent Calendar 2017 21日目の記事になります。
ちょっと話が前後してしまいますが、火曜日にginra.rb に初参加してきたのでそのまとめを。
今回は Ruby 2.5 のおさらいということで Ruby 2.5 の機能を参加者全員で眺めて生きながら意見を言っていくような内容でした。

当日読んでいた Ruby 2.5 に関する資料

以下、気になった機能を読んでいるときに出た意見などの適当なまとめ。
ちなみにわたしはまだ Ruby 2.5 の機能は殆ど触れてないです。

バックトレースおよびエラーメッセージが逆順に出力されるようになった

以下のようにバックトレースの順番が代わりました。

Ruby 2.4

$ ruby2.4 main.rb 
main.rb:3:in `map': undefined method `to' for 1:Integer (NoMethodError)
  from main.rb:3:in `homu'
    from main.rb:8:in `func'
  from main.rb:11:in `<main>'

Ruby 2.5

$ ruby main.rb
Traceback (most recent call last):
    3: from main.rb:11:in `<main>'
  2: from main.rb:8:in `func'
  1: from main.rb:3:in `homu'
main.rb:3:in `map': undefined method `to' for 1:Integer (NoMethodError)
  • 慣れの問題
  • リリースされたらみんな使うのでいろいろと意見が出てきそう
  • わたしはまだ確認してないのでなんとも言えない

トップレベルの定数検索が Object から削除された

以下の用にトップレベルで定義したクラスなどが X::Toplevel のように :: で参照できなくなった。

class Toplevel
end

class X
end

# この時にトップレベルの定数(クラスなど)が参照できなくなった
X::Toplevel
  • 今までの仕様は挙動としては正しいけど意図しているかは微妙だった
  • Ruby の挙動として見ると Object が検索されるのはわかる
  • Object 内で明示的に定義したクラスも参照されない
class Object
  class X
  end
end

class X2
end

# エラー
X2::X

yield_self

#tap と似ているがブロック内の戻り値を返す。

# yield_self だと break は不要
p [1, 2, 3].yield_self { |myself|
    myself.map { |it| it + myself.size }
}
# => [4, 5, 6]

Procの確保を遅延

  • 今まではブロック引数を明示化しているよりも『していないほうが』パフォーマンスがよかった
  • ブロック引数を明示化したほうがわかりやすいのでなるべくパフォーマンスに影響がでないように対応した
  • jruby だと逆にブロック引数を明示化したほうが早かった
  • パフォーマンスの問題はなくなったのでみんなブロック引数を明示化しよう
  • Ruby 2.5 は引数に &block を書いても速い!!! - onk.ninja

式展開の #to_s が refinements に対応

class X
end

using Module.new {
    refine X do
        def to_s
            "class X"
        end
    end
}

# refinements で定義された #to_s が呼ばれる
p "output: #{X.new}"
# => "output: class X"
  • 便利
  • ほかの内部で呼ばれるメソッドも refinements に対応してほしい
  • respond_to?to_proc も refinements に対応してほしい

Integer.sqrt

p Integer.sqrt(9)
#=> 3

p Integer.sqrt(15)
#=> 3

p Integer.sqrt(16)
#=> 4

String#casecmp で例外が発生しなくなった

Ruby 2.4

# Error no implicit conversion of Symbol into String (TypeError)
"homu".casecmp(:homu)

# Error no implicit conversion of Symbol into String (TypeError)
"homu".casecmp(1234)

Ruby 2.5

p "homu".casecmp(:homu)
# => nil
p "homu".casecmp(1234)
# => nil
  • TypeError のままでもよかったのでは?
  • Symbol だと nil を返していた
  • 一貫性のために TypeError から nil を返すように変更
    • と思ったけど Symbol でも TypeError だったのでうーん?
  • やっぱり TypeError のままの方がよかったのでは?

ERB#result_with_hash が追加

require "erb"

p ERB.new("Hello, <%=name%>").result_with_hash(name: "homu")
# => Hello, homu
  • #result_with_hash でコンテキストを渡した場合でも外部のコンテキストは参照できる
  • それならあまり意味が無いような気がする
  • コンテキストを明示的に上書きすることはできる

Set#===

case :apple
# Set のいずれかに含まれていれば
when Set[:potato, :carrot] then 'vegetable'
when Set[:apple, :banana]  then 'fruit'
end
#=> "fruit"
  • 集合と集合で比較するのではなくて #include? を使う
  • Set オブジェクト同士を比較したい?
  • #include? を呼び出すのは微妙かと思ったけどまあ Set ならいいかー
  • Method#=== を使用して s.method(:include?) みたいに代用することも可能

個人的に #===#include? にするのはなんかなーと思ってましたがまあ Set だからいいかーという位置に落ち着きました。
Range も同じですしね。
ただし、Array#=== を #include? のエイリアスにするのは絶対に許さん。

Ruby 2.5 に対する所感

そんな感じでざっくりまとめてみました。
上記以外にも Ruby 2.5 ではいろいろとメソッドや機能なんかが追加されたんですが割と『それはそうだよねー』みたいな機能とかが多かった印象です。

個人的によさげなのはやっぱりブロック引数周りのパフォーマンスの向上ですかね。
そもそもブロック引数を明示化してないのにブロック引数を渡すことができるという挙動があまり好きではないので、この機会に『ブロック引数を受け取る場合は必ずブロック引数を明示化する』ようになってほしいです。
あと Method#=== もそうですが Set#=== が入っていたのが知らなかったのでちょっと驚きました。
#=== ブーム来てるぞ…。

他には全員に影響があるって意味でバックトレース順が変わることですかねえ。
慣れの問題だったり、Ruby 2.5 の仕様のほうが見やすいって人もいると思いますが、やはり今までと表示形式が変わることに対する影響はコードの仕様が変わるぐらい大きいと思います。
この部分は Ruby 2.5 がリリースされてから色々と意見が出てきそうですねえ。
あ、あとそろそろ refinements さんはどこまでの範囲が影響するのかをしっかりとまとめてほしいところ。

参加してみて

そんな感じで初参加してきましたー。
参加する前はもくもく会みたいな感じで各々が Ruby 2.5 について調べるのかと思っていたのですが、実際は全員で機能を眺めて行くような感じでした。

会自体そこまで重い雰囲気ではなかったので割りと好き勝手意見を言うことができたり、他の人の意見なんかも聞くことができたのでとてもよかったです。
基本的にぼっちなのでこういうみんなで Ruby に対する意見を言い合うような機会は殆ど無いのでとても新鮮でした。
思っていることは割りとみんな同じなんだなーという風に共感できたのもよかったですね。

あと参加者の殆どは Ruby ユーザ(Ruby 本体の開発者ではない)なのでそういう目線で自由に意見を言っているのもいいなーと思いました。
結果的に Ruby 2.5 で追加される機能がなんとなく理解できたので参加してよかったです。
運営の方がありがとうございました。