2022/08/04 今回の気になった bugs.ruby のチケット

今週は Enumerator.product が新しく追加されました。

[Bug #18953] Array#uniq doesn't evaluate the given block when the size of the array is one

  • 配列の要素が1つの場合に Array#uniq のブロックが呼ばれないんだけどこれは期待する挙動?というバグ報告
# これはエラーにならないが
[1].uniq { aaa }

# これはエラーになる
# error: undefined local variable or method `aaa' for main:Object (NameError)
[1, 2].uniq { aaa }
  • これ自体は期待する挙動ぽいですが、実装依存になるんですかね?
  • また Array#sort_by #max_by #min_by は要素が1つの場合でもブロックが呼び出されます
# error: undefined local variable or method `aaa' for main:Object (NameError)
[1].sort_by { aaa }
  • あと Enumerable#uniq は要素が1つの場合でもエラーになるみたいですね
require "set"

# 2つともエラーになる
Set[1].uniq { aaa }
{ a: 1 }.uniq { aaa }

[Feature #18950] Hash#slice fails to copy default block

  • Hash#slice がデフォルトの proc をコピーして返さないバグ報告
hash_with_default = Hash.new { |h, k| h[k] = {} }

# デフォルト proc が設定されている
pp hash_with_default.default_proc
# => #<Proc:0x00007f7cd0f3c8a8 /tmp/vdmem8a/29:1>

# #slice の戻り値はデフォルト proc が設定されていない
pp hash_with_default.slice(:a).default_proc
# => nil
  • これは期待する挙動で Hash#slice は新しい Hash オブジェクトを返してその時にデフォルトの proc はコピーされないみたいです
  • 以下のように Hash#slice 以外でも同様の挙動
hash_with_default = Hash.new { |h, k| h[k] = {} }
pp hash_with_default.except(:a).default_proc   # => nil
pp hash_with_default.select {}.default_proc    # => nil
pp hash_with_default.invert.default_proc       # => nil

[Bug #18743] Enumerator#next / peek re-use each others stacktraces

enum = [1, 2, 3].each
# Enumerator#peek は状態を変化させないで「次」を返す
pp enum.peek   # => 1
pp enum.peek   # => 1
pp enum.peek   # => 1

# Enumerator#next は状態を変化させて「次」を返す
pp enum.next   # => 1
pp enum.next   # => 2
pp enum.next   # => 3

# 「次」がない場合はエラーになる
# error: `next': iteration reached an end (StopIteration)
pp enum.next   # => 3
  • 次のように peek で失敗したした後に next でエラーになるとバックトレースの行数が正しくないというバグ報告
# enum.rb             # 1
                      # 2
enum = [].each        # 3
enum.peek rescue nil  # 4   <- エラー行はここを指している
enum.next             # 5   <- ここでエラーになるが↑
$ ruby enum.rb
enum.rb:4:in `peek': iteration reached an end (stopiteration)
    from enum.rb:4:in `<main>'
  • 以下のように複数呼び出した場合も意図しない行数になっている
# enum.rb                # 1
                         # 2
enum = [].each           # 3
enum.peek rescue nil     # 4
enum.next rescue nil     # 5
enum.peek rescue nil     # 6
puts "line #{__line__}"  # 7
enum.next                # 8
$ ruby enum.rb
line 7
enum.rb:4:in `peek': iteration reached an end (stopiteration)
    from enum.rb:4:in `<main>'

[feature #18685] Enumerator.product: cartesian product of enumerables

  • 要素ごとの全ての組み合わせの Enumerator を生成する Enumerator.product を追加する提案
product = Enumerator.product(1..3, ["a", "b"])
p product.class #=> Enumerator

product.each do |i, c|
  puts "#{i}-#{c}"
end
__end__
output:
1-a
1-b
2-a
2-b
3-a
3-b
  • この機能は ruby 3.2 にマージされました