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

今週は Kernel#then に引数を渡せるようにする提案がありました。

[Feature #18690] Allow Kernel#then to take arguments

  • Kernel#then に引数を追加する提案
  • 通常はレシーバをブロックの引数として受け取るが
1.5.then{|x| Math.atan(x)}
  • #then に渡した引数をブロックの引数でも受け取るようにする提案
3.then(4){|x, y| Math.hypot(x, y)}
  • チケットでは
honyarara.then{|x|
  foo(x)
  bar(fugafugafuga)
  baz(hogehogehoge)
  qux(x, fugafugafuga, hogehogehoge)
}
  • の場合に
honyarara.then(fugafugafuga, hogehogehoge){|x, y, z|
  foo(x)
  bar(y)
  baz(x)
  qux(x, y, z)
}
  • とかけるユースケースが提示されている
  • then の中で複数回同じメソッドを呼ぶ場合だと便利そうですかね?
obj.bar.then(hoge.foo) { |bar, foo| foo.piyo(foo, bar, foo) }

[Bug #18688] when array's default value is empty hash adding a hash key value changes all array elements

  • 以下のように空の Hash の配列の要素を書き換えると全て書き変わってしまうというバグ報告
# {} が3つある配列を生成する
ah = Array.new(3, {})
p ah    # => [{}, {}, {}]

# 1つの要素を書き換える
ah[1][:foo] = 'bar'

# この時に配列の要素は以下のようになる
p a
# 期待する挙動 => [{}, {:foo=>"bar"}, {}]
# 実際の挙動   => [{:foo=>"bar"}, {:foo=>"bar"}, {:foo=>"bar"}]
  • これは配列の要素の参照先が全て同じになっているので意図する挙動になる
# これは参照しているオブジェクトがバラバラ
a = [{}, {}, {}]
p a[0].__id__   # => 60
p a[1].__id__   # => 80
p a[2].__id__   # => 100

# Array.new の場合は全て同じオブジェクトを参照している
a = Array.new(3, {})
p a[0].__id__   # => 120
p a[1].__id__   # => 120
p a[2].__id__   # => 120


# n 個の同じ要素の配列を定義したい場合はブロックを渡して定義するのが安全
a = Array.new(3) { {} }
p a[0].__id__   # => 60
p a[1].__id__   # => 80
p a[2].__id__   # => 100

[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

=begin output
1-A
1-B
2-A
2-B
3-A
3-B
=end
  • Array#product はあるんですがそれとは別に Enumerable でも使いたいって感じですかね
# Enumerable に対しては使えないので Array に変換する必要がある
pp (1..3).to_a.product(["A", "B"])
# => [[1, "A"], [1, "B"], [2, "A"], [2, "B"], [3, "A"], [3, "B"]]

[Feature #18618] no clobber def

  • 以下のようにスーパークラスのメソッドがある場合は再定義しないようなコードがある
class Dog
  def bark
    'bark!'
  end
end

class Poodle < Dog
  # 既に同名のメソッドが定義されていれば例外にする
  raise if method_defined? :bark
  def bark
    'bow-wow'
  end
end
  • これを次のように ncdef でかけるようにしたいという提案
class Dog
  def bark
    'bark!'
  end
end

class Poodle < Dog
  ncdef bark        # "no clobber" def
    'bow-wow'
  end
end

# => #<MethodAlreadyDefined: Method `bark' already defined.>
  • ApplicationRecord とかのサブクラスを定義したい時にこういうことをしたいらしい
  • 意図しないメソッドの上書きってどれぐらい弊害があるんですかね…
    • これがあると全部 ncdef で定義する必要がありそう
  • 他の言語だと逆に『スーパークラス側でメソッドが上書きできないこと』を明示化するような機能は存在する
  • C++ の例だと以下のような感じ
// 基底クラス
class base {
  // final が付いていると派生クラスで再定義できない
  virtual void func_final() final;
};

// 派生クラス
class sub : base {
  // error: error: declaration of 'func_final' overrides a 'final' function
  virtual void func_final() final;
};