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

今週は Enumerable#each_cons#each_slice の戻り値の変更がありました。

[PR #1509] Fix Enumerable#each_cons and each_slice to return a receiver

  • Enumerable#each_consEnumerable#each_slice の戻り値をレシーバにする PR
    • 現在は nil を返している
  • 2017年の PR で最近マージされた
    • チケットは特になさそう?
p [1, 2, 3].each_cons(2) {}
# Ruby 3.0 => nil
# Ruby 3.1 => [1, 2, 3]

[Bug #18268] Behavior change when each_cons and break ... if false are combined in Ruby 3.1

[Feature #16663] Add block or filtered forms of Kernel#caller to allow early bail-out

  • #caller に走査できる機能を追加しようという提案
    • この機能を追加することでスタックトレースを取得する際のオーバーヘッドを軽減させる目的
  • 例えば以下のようにブロックを渡すと以下のように利用できる
def find_matching_frame(regex)
  caller do |frame|
    # ファイル名が regex にマッチしたら早期 return する
    return frame if frame.file =~ regex
  end
end
  • また以下のように先頭4つを受け取るようにしたりとか
i = 0
ary = []
caller { |x|
  ary << x
  i += 1
  break if i == 4
}

[Feature #14394] Class.descendants

  • .descendants はレシーバが『継承されている』クラス/モジュールの一覧を返すメソッドの追加の提案
module A
end

module B
  include A
end

module C
  include B
end

A.descendants    #=> [A, C, B]
B.descendants    #=> [B, C]
C.descendants    #=> [C]
class A; end
class B < A; end
class C < B; end

pp A.descendants    #=> [B, C]
pp B.descendants    #=> [C]
pp C.descendants    #=> []

[Bug #18267] Argument forwarding requires parenthesis on method definitions

  • def hoge(foo, ...)def hoge foo, ... みたいに () を省略したいという提案
  • パーサ的にめっちゃむずそうな雰囲気があるんだけどどうなんだろう…
  • ちなみに ... 引数をすべて別のメソッドに forward する構文
def foo(...)
  # foo の引数をすべて bar に渡す
  bar(...)
end

[Feature #18275] Add an option to define_method to not capture the surrounding environment

  • define_method で定義したメソッドが Ractor から参照できない
def twice(a)
  a + a
end

define_method(:twice2) { |a|
  a + a
}


ractor = Ractor.new(10) do |loop_count|
  $stdout.sync = true
  # def で定義した twice は参照できる
  p "twice: #{twice loop_count}"

  # define_method で定義した twice2 は参照できない
  p "twice2: #{twice2 loop_count}"
end

ractor.take
  • これを解消するために define_method に変数などをキャプチャしないようにするオプションを追加する提案
    • 不要な変数までキャプチャすると遅くなってしまう
    • 明示的にキャプチャしないようにすることで Ractor から参照できるようにする
some_random_thing = “a” * 10000
some_captured_block = -> { ... }

# capture_environment: でキャプチャするかどうかを制御する
define_method(:my_method, capture_environment: false, &some_captured_block)
some_random_thing = “a” * 10000 # kept because `my_method` needs it
another_random_thing = “b” * 10000 # not kept
some_captured_block = -> { ... }

# 特定の変数のみをキャプチャする
define_method(:my_method, needs: [:some_random_thing], &some_captured_block)
# Proc を make_shareable 化しておけば OK
body = -> (a) {
  a + a
}
Ractor.make_shareable(body)
define_method(:twice, &body)

ractor = Ractor.new(10) do |loop_count|
  $stdout.sync = true
  # twice は参照できる
  p "twice: #{twice loop_count}"
end

ractor.take

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

  • private の戻り値をレシーバから引数を返すようにする提案
class X
  def hoge
  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

[Feature #11689] Add methods allow us to get visibility from Method and UnboundMethod object.

class Object
  def debugging(name)
    original = instance_method(name)

    # 提案
    method_visibility = original.visibility
    # 既存の実装だとこう書く必要がある
    # method_visibility = if private_method_defined?(name)
    #                       :private
    #                     elsif protected_method_defined?(name)
    #                       :protected
    #                     else
    #                       :public
    #                     end
    define_method(name) { |*args, &block|
      pp name
      original.bind(self).call(*args, &block)
    }

    # 元のメソッドに合わせる
    send(method_visibility, name)
  end
end

class X
  def hoge
    pp 1 + 2
  end
  private :hoge

  debugging :hoge
end

# error: private method `hoge' called for #<X:0x000055f0b1abf3e0> (NoMethodError)
X.new.hoge