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

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

[Feature #17097] map_min, map_max

  • #map_min #map_max を追加する提案
# 以下のようなコードを
%w[aa b cccc dd].max_by(&:length).length # => 4
%w[aa b cccc dd].map(&:length).max # => 4

# こう記述するイメージ
%w[aa b cccc dd].map_max(&:length) # => 4
  • 確かにあると便利そうな気がするがこういうのを上げるときりがないような気もする
  • #filter_map みたいにパフォーマンスが改善する例があればワンチャン…?

[Bug #17058] Array#delete_if doesn't change array instantly

  • Array#delete_if で条件にあった要素を削除する事ができる
a = [1, 2, 3, 4, 5, 6]
# 偶数を削除する
a.delete_if { |it|
  it.even?
}
p a
# => [1, 3, 5]
  • このメソッドのドキュメントでは『ブロックが呼ばれるたびにレシーバに即座に反映される』と書かれている
  • しかし、実際の挙動は #delete_if の処理が終わったあとに反映されている
a = [1, 2, 3, 4, 5, 6]
# 偶数を削除する
a.delete_if { |it|
  puts "During iteration: #{a}"
  it.even?
}
p a
# 期待する挙動:
# During iteration: [1, 2, 3, 4, 5, 6]
# During iteration: [1, 2, 3, 4, 5, 6]
# During iteration: [1, 3, 4, 5, 6]
# During iteration: [1, 3, 4, 5, 6]
# During iteration: [1, 3, 5, 6]
# During iteration: [1, 3, 5, 6]
# [1, 3, 5]

# 実際の動作:
# During iteration: [1, 2, 3, 4, 5, 6]
# During iteration: [1, 2, 3, 4, 5, 6]
# During iteration: [1, 2, 3, 4, 5, 6]
# During iteration: [1, 3, 3, 4, 5, 6]
# During iteration: [1, 3, 3, 4, 5, 6]
# During iteration: [1, 3, 5, 4, 5, 6]
# [1, 3, 5]

[Bug #17096] attr_accessor doesnt work

  • attr_accessor がなんかおかしくない?というバグチケ
  • 挙動がわかりづらいんですが以下のような結果になる
class A
  def initialize(type:)
    @type = type
  end

  def b
    p type       # => "hoge"
    p type.nil?  # => false
    type = 'default' if type.nil?
    # type.nil? が false なのになぜか type = 'default' されてる
    type         # => "default"
  end

  private

  attr_accessor :type
end

p A.new(type: "hoge").b
  • 初見だと意図しない挙動に見えるんですが実は Ruby だと意図する挙動になります
  • 実際の挙動は以下の通り
def b
  # ここは #type メソッドが呼ばれる
  # なので type.nil? は false
  p type
  p type.nil?

  # この行で暗黙的に type 変数が定義される
  # 暗黙的に定義された type 変数は nil で初期化される
  # なので type.nil? == true となり type = 'default' が呼ばれる
  type = 'default' if type.nil?
  type
end
  • type = 'default' if type.nil? がポイントで Ruby の場合はパース時に代入式が存在すると暗黙的に type という変数を定義します
  • 例えば次のように実際には呼ばれてないけど変数は定義される
# if 式の中身が呼ばれなくても変数 a は定義される
if false
  a = 42
end

# 変数 a は nil で定義される
p a
# => nil
  • なので次のようにメソッドと同名の変数を定義する場合は注意する必要がある
def value
  42
end

# メソッドと同名の変数を定義する場合は要注意
# map 内で呼び出す value はメソッドではなくて変数を参照する
value = (1..10).map { |it| it.to_s + value.to_s }
p value
# => ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]

[Feature #17099] Remove boolean argument and warning from Module#attr

  • Module#attr#attr_reader と同等だが第二引数に true を渡すと書き込み用メソッドも定義される
class X
  # #hoge だけ定義される
  attr :hoge

  # #foo と #foo= が定義される
  attr :foo, true
end
  • しかし、第二引数は非推奨で -W を付けると警告が出る
class X
  # warning: optional boolean argument is obsoleted
  attr :value, true
end

[Bug #16695] Stack consistency error when using the return value

  • x = false; y = (return until x unless x) だけで segv するらしい
  • Ruby むずかしいな?