2021/11/25 今回の気になった bugs.ruby のチケット

今週は load メソッドにモジュールを渡せるようになりました。

[Bug #17675] StringIO#each_byte doesn't check for readabilty while iterating

  • IO がクローズしているのにイテレーションが処理されてしまうというバグ報告
require "stringio"

strio = StringIO.new("1234")
strio.each_byte do |byte|
  puts byte
  # ここでクローズしているがイテレーションは引き続き処理されている
  strio.close
end
# => 49
#    50
#    51
#    52
  • このバグは修正済みで最新版では IOError が発生する
    • おそらくマイナーバージョンでバックポートされる
require "stringio"

strio = StringIO.new("1234")
strio.each_byte do |byte|
  puts byte
  strio.close
end
# => 49
#    error: `each_byte': not opened for reading (IOError)

[Misc #18352] What is the Hash#grep expected?

  • Hash#grep はなにを期待しているのか?という質問
  • Array#grep の場合は以下のように利用できる
[:foo1, :foo2, :bar].grep /foo/
# => [:foo1, :foo2]
  • Hash#grep は以下のようになりなにを期待しているのかがよくわからない
{foo: '100', bar: '200'}.grep /foo/
# => []
class HashMatcher < Struct.new(:key, :value, keyword_init: true)
  def ===((k, v))
    (key.nil? || key === k) && (value.nil? || value === v)
  end
end

{foo1: '100', foo2: '200', bar: '200'}.grep(HashMatcher[key: /foo/])
# => [[:foo1, "100"], [:foo2, "200"]]

[Feature #18262] Enumerator::Lazy#partition

  • 以下のような場合はファイル全部が読み込まれてしまう
file = File.open('very-large-file.txt')
lines_with_errors, lines_without_errors = file.lazy.partition { _1.start_with?('E:') }
lines_with_errors.class
# => Array, all file is read by this moment
  • これを改善するために Enumerator::Lazy#partitionEnumerator::Lazy を返すようにする提案
  • 以下のように使えるのを想定している
# Enumerator.produce で 1, 2, 3,... という無限Range を生成する
Enumerator.produce(1) { |i| puts "processing #{i}"; i + 1 }.lazy
  .take(30)
  .partition(&:odd?)
  .then { |odd, even|
    # partition の戻り値から必要な数だけを取得する
    p odd.first(3), even.first(3)
  }
# Prints:
# processing 1
# processing 2
# processing 3
# processing 4
# processing 5
# [1, 3, 5]
# [2, 4, 6]
  • これは普通に便利そう

[Feature #12495] Make "private" return the arguments again, for chaining

  • private の戻り値をレシーバから引数を返すようにする提案
class X
  def hoge; end
  p private :hoge
  # 現在の挙動   => X
  # 変更する挙動 => :hoge
end
  • 以下のようなことをやりたい
def cached(name)
  # Rewrite method to include a cache
  return name
end

# これは OK
private cached def foo() end

# こうもかけるようにしたい
cached private def foo() end
  • これはマージされて Ruby 3.1 から挙動が変わる
class X
  def hoge; end
  p private :hoge
  # Ruby 3.0 => X
  # Ruby 3.1 => :hoge
end

[Feature #11256] anonymous block forwarding

  • 仮引数を定義してブロックを受け取ると遅いので仮引数を定義しないでブロックをフォワードしたいという提案
  • Ruby 3.1 から & だけ定義できるようになった
def foo(a)
  yield a
end

def bar(&)
  # ブロック引数を foo にフォワードする
  foo(1, &)
end

bar { p _1 }
  • また & を省略する事はできない
def foo(&) = bar(&) # OK
def foo = bar(&)    # NG
  • またブロックからは使えないぽい?
def hoge; end
# no anonymous block parameter
proc { |&| hoge(&) }

[Feature #6210] load should provide a way to specify the top-level module

  • load メソッドに Module を指定できるようにする提案
  • 以下のように特定のモジュールに対して load した Ruby のコードが展開される
# test.rb
def hoge
  "hoge"
end

class Foo
  def foo
    "foo"
  end
end
module M
end

# M に対して test.rb の中身が定義される
load "./test.rb", M

p M::Foo
# => M::Foo

p M::Foo.new.foo
# => "foo"

class X
  include M
  public :hoge
end

p X.new.hoge
# => "hoge"
  • これがマージされて Ruby 3.1 からこの機能が使えるようになった

[Feature #18331] Kernel.#Time

  • Kernel.#Integer などと同じような Kernel.#Time を追加する提案
    • Time.new の短縮形としてほしいみたい
Time("2021-11-13T21:21:18.027294 +0900") # => 2021-11-13 21:21:18.027294 +0900
Time("2021-11-13 21:21:18.027294 +0900") # => 2021-11-13 21:21:18.027294 +0900
Time("foo", exception: false) # => nil