2020/08/13 今週の気になった bugs.ruby

内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。

[Feature #15504] Freeze all Range object

  • リテラルRange を生成すると同じ id のオブジェクトが返ってくるケースがある
# これは個別の id が返ってくる
p (1..3).__id__  # => 60
p (1..3).__id__  # => 80
p (1..3).__id__  # => 100

def hoge
  (1..3).__id__
end
# これは同じ id が返ってくる
p hoge    # => 120
p hoge    # => 120
p hoge    # => 120
  • これの影響で次のように奇妙な動作をすることがある
2.times{
  r = (1..3)
  p r.instance_variable_get(:@foo)
  #=> 1st time: nil
  #=> 2nd time: :bar
  r.instance_variable_set(:@foo, :bar)
}
  • こういう奇妙な問題を回避するために frozen した値を返しませんか?という提案
  • frozen された Range でも次のように中の値が変わってしまうことがある
r = ('a'..'z').freeze
r.end.upcase!
p r # => "a".."Z"

[Feature #17054] Some NilClass methods are faster if implemented in Ruby

  • NilClass#to_i などを C言語じゃなくて Ruby で実装することで高速化するというチケット
  • これは Ruby 側でメソッドをキャッシュしており、実行中に大量のメソッドを呼んだ場合に C言語実装よりも早くなるケースがあるらしい
    • NilClass#to_iNilClass#to_fRuby 実装の方が早いらしい
    • NilClass#to_h は空の Hash オブジェクトを生成する必要があるので Ruby 実装だと遅いらしい
  • こういう実は C言語よりも Ruby で実装すると高速化する。みたいなのがあるのが面白い

[Bug #17101] YAML.load_file: Massive slowdown under Ruby 2.7 vs. Ruby 2.4

  • YAML.load_file の速度が Ruby 2.4 と比べて Ruby 2.7 がだいぶ遅くなっているという報告チケット
  • 実際のベンチマーク
sh@MyComputer:~/rubydev/bugs$ benchmark-driver yml.yaml --output compare --rbenv '2.4.4;2.5.8;2.6.6;2.7.1;2.8.0-dev'
Calculating -------------------------------------
                          2.4.4       2.5.8       2.6.6       2.7.1   2.8.0-dev
           load_file      0.113       0.075       0.076       0.058       0.134 i/s -       1.000 times in 8.839451s 13.406090s 13.240691s 17.256679s 7.435239s

Comparison:
                        load_file
           2.8.0-dev:         0.1 i/s
               2.4.4:         0.1 i/s - 1.19x  slower
               2.6.6:         0.1 i/s - 1.78x  slower
               2.5.8:         0.1 i/s - 1.80x  slower
               2.7.1:         0.1 i/s - 2.32x  slower
  • これを見てみると 2.7.1 では遅いが 2.8.0dev ではだいぶ高速化している

[Bug #17105] A single return can return to two different places in a proc inside a lambda inside a method

  • Ruby では ブロック内で return したときに proclambda で挙動が異なる
# proc 内で return するとその時点でメソッドから抜ける
def hoge
  block = proc { return :return_proc }
  [:return_hoge, block.call]
end

p hoge
# => :return_proc

# lambda 内で return するとそのブロックから抜ける
def hoge
  block = lambda { return :return_lambda }
  [:return_hoge, block.call]
end

p hoge
# => [:return_hoge, :return_lambda]
  • しかし、次のように lambda 内で proc から return した場合はその限りではなくなる
# lambda でラップするとメソッドからではなくて lambda から抜ける
def hoge
  block = lambda {
    block = proc {
      # ここで return すると lambda から抜ける
      return :return_proc
    }
    block.call
    :return_lambda
  }
  [:return_hoge, block.call]
end

p hoge
# => [:return_hoge, :return_proc]


# proc でラップした場合はメソッドから抜ける
def hoge
  block = proc {
    block = proc {
      # ここで return するとメソッドから抜ける
      return :return_proc
    }
    block.call
    :return_lambda
  }
  [:return_hoge, block.call]
end

p hoge
# => :return_proc
  • この挙動は奇妙なのでこれが意図しているのかどうか議論するチケットになっている、多分

[Feature #13560] Module#attr_ methods return reasonable values[Feature #9453] Return symbols of defined methods for attr and friends

  • attr_xxx に戻り値を追加する提案
    • いまは nil が返ってくる
attr_reader :hoge
# => [:hoge]
attr_writer :foo, :bar
# => [:foo, :bar]
attr_accessor :piyo
# => [:piyo, :piyo=]
  • これを利用すると次のようにして attr_reader しつつ private などができる
private *attr_reader :hoge, :foo
private_attr_reader :hoge, :foo