今週の気になった bugs.ruby

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

[Feature #16923] Switch reserved for numbered parameter warning to SyntaxError

  • _1 という名前は Numbered parameter で使用されており、同名の変数やメソッドを定義するとエラーになるようになる
    • 2.7 現在では警告がでている
  • 次期 Ruby では以下のような挙動になる
# 2.7 : warning: `_1' is reserved for numbered parameter; consider another name
# dev : error: _1 is reserved for numbered parameter
_1 = 42

# 2.7 : warning: `_1' is reserved for numbered parameter; consider another name
# dev : error: _1 is reserved for numbered parameter
def _1
end
  • また define_method で動的に _1 という名前のメソッドを定義することは可能
# 2.7 : no warning
# dev : no warning, no error
define_method(:_1) {
  42
}

p _1
# => 42

[Feature #17056] Array#index: Allow specifying start index to search like String#index does

str = "foo hoge foo hoge"

# "hoge" の開始位置を返す
p str.index("hoge")
# => 4

# 6 文字目以降で検索する
p str.index("hoge", 6)
# => 13

[Feature #17016] Enumerable#scan_left

p (1..).lazy.enum_for(:inject, 0).map {|a, b| a + b }.take(10).force
# => [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]

# もしくは
# p (1..).lazy.enum_for(:inject, 0).map {|a, b| a + b }.first(10)
# 銀行の入出金履歴
gains = [+3000, -2000, +2000, -1000]

# 残高の履歴を計算
sums = [0]
(1..gains.length).each do |i|
  sums[i] = sums[i - 1] + gains[i - 1]
end
pp sums
# => [0, 3000, 1000, 3000, 2000]

# scan_left を使うとシュッとできる
sums = gains.scan_left(0, &:+)
pp sums
# => [0, 3000, 1000, 3000, 2000]
  • #scan_left という名前はあんまりよろしくないと言うことで別の名前の提案が出てる
    • #reflect#project#interject など
  • #reflect の実装 : https://github.com/ruby/ruby/pull/3358
# reflect
(0...0).reflect(:+) # => []
(5..10).reflect(:+) # => [5, 11, 18, 26, 35, 45]
(5..10).reflect { |sum, n| sum + n } # => [5, 11, 18, 26, 35, 45]
(5..10).reflect(1, :*) # => [1, 5, 30, 210, 1680, 15120, 151200]
(5..10).reflect(1) { |product, n| product * n } # => [1, 5, 30, 210, 1680, 15120, 151200]o
# lazy.reflect
(0..Float::INFINITY).lazy.reflect(:+).first(4) # => [0, 1, 3, 6]
(0..Float::INFINITY).lazy.reflect(:+).first(4) # => [1, 1, 2, 4]
enum = (5..10).lazy.reflect(:+) # => #<Enumerator::Lazy: #<Enumerator::Lazy: 5..10>:reflect(:+)>
6.times.map { enum.next } # => [5, 11, 18, 26, 35, 45]
enum = (5..10).lazy.reflect(1, :*) # => #<Enumerator::Lazy: #<Enumerator::Lazy: 5..10>:reflect(1, :*)>
7.times.map { enum.next } # => [1, 5, 30, 210, 1680, 15120, 151200]
(1..).lazy.inject(0, :+).first(10) #=> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

[Bug #17017] Range#max & Range#minmax incorrectly use Float end as max その後

  • Bug #17017 の対応により Range#max Range#minmax の挙動が少し変わった
p (1..3.1).max
# 2.7 : => 3.1
# dev : => 3

p (1..3.1).minmax
# 2.7 : => [1, 3.1]
# dev : => [1, 3]
p (42..Float::INFINITY).max
# 2.7 : => Infinity
# dev : error: `floor': Infinity (FloatDomainError)
p (42..Float::INFINITY).max
# 2.7 : => Infinity
# dev : => Infinity

[Feature #17055] Allow suppressing uninitialized instance variable and method redefined verbose mode warnings

  • Ruby では -W を付けて実行したり $VERBOSE = true にすると『メソッドを再定義した場合』や『未定義のインスタンス変数を参照』した時に警告が出るようになる
# 警告を出すようにする
$VERBOSE = true

class X
  def initialize
    @init = 42
  end

  def hoge; end

  # 同名のメソッドを再定義すると警告が出る
  # warning: method redefined; discarding old hoge
  # warning: previous definition of hoge was here
  def hoge; end

  def foo
    # no warning
    @init

    # 未定義のインスタンス変数を参照すると警告ができる
    # warning: instance variable @no_init not initialized
    @no_init
  end
end

p X.new.foo
  • この警告を実行時に Ruby のコードで制御する機能を追加する提案のチケット
    • .expected_redefined_method? : メソッド再定義の警告を制御
    • #expected_uninitialized_instance_variable? : 未定義のインスタンス変数を参照したときの警告を制御
  • これは以下のように使用できる
# 警告を出すようにする
$VERBOSE = true

class X
  # メソッドが再定義されたときに呼ばれるコールバックメソッド
  # 引数には再定義されたメソッドの名前を Symbol で受け取る
  # true を返した場合は警告が出なくなる
  def self.expected_redefined_method?(name)
    name == :hoge
  end

  # 未定義のインスタンス変数が呼ばれたときのコールバックメソッド
  # 引数にはインスタンス変数の名前を Symbol で受け取る
  # true を返した場合は警告が出なくなる
  def expected_uninitialized_instance_variable?(name)
    name == :@no_init
  end

  def initialize
    @init = 42
  end

  def hoge; end

  # .expected_redefined_method? が true を返すので警告が出ない
  # no warning
  def hoge; end

  def bar; end

  # .expected_redefined_method? が false を返すので警告が出ない
  # warning: warning: method redefined; discarding old bar
  # warning: instance variable @warning_init not initialized
  def bar; end

  def foo
    # no warning
    @init

    # #expected_uninitialized_instance_variable? が true を返すので警告が出ない
    # no warning
    @no_init

    # #expected_uninitialized_instance_variable? が false を返すので警告が出る
    # warning: instance variable @no_init not initialized
    @warning_init
  end
end

p X.new.foo