2021/01/07 今週の気になった bugs.ruby のチケット

内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。

[Feature #17471] send_if method for improved conditional chaining

  • 以下のような #send_if を追加する提案
class Object
  def send_if(method, *args, proc: nil)
     yield(self) ? self.send(method, *args, &proc) : self
  end
end
  • これは以下のように『条件にマッチしたときのみメソッドチェーンを行う』ような場合に利用できる
puts 'Do you want a loud Merry Christmas? (y or I take it as a no)'
answer = gets.chomp

# 標準入力を受け取り、入力によって呼び出す処理を切り替える
# 'y' が入力されたら大文字に変換して結合する
puts %w(Merry Christmas).send(:map, &->(e) { answer == 'y' ? e.upcase : e }).join(' ')

# 提案されている send_if ならこんな感じで記述できる
puts %w(Merry Christmas).send_if(:map, proc: :upcase) { answer == 'y' }.join(' ')
  • わたしもこういうのがほしいと思っていていろいろと考えているんですが今回の #send_if はかなりやってることが多くて個人的にはちょっと微妙
    • とはいえこうなってしまう理由もわかるのでうーーーーんって感じ
    • 条件条件にマッチした場合の処理 の2つのブロックをメソッドに渡したいんですが Ruby でこのあたりどう表現すべきなのかが難しい
  • ちなみに個人的には以下のような tap + break でいいじゃん、ってなっています
# Proposal
puts %w(Merry Christmas).send_if(:map, proc: :upcase ) { answer == 'y' }.join(' ')

# tap + break
puts %w(Merry Christmas).tap { break _1.map(&:upcase) if answer == 'y' }.join(' ')
  • tap + break に慣れてないとぎょっとするんですが、これはこれで Ruby としてみるとかなり素直なコードになっているので個人的には気に入っています
  • また似たようなチケットとしては以下のようなチケットもあるので気になる人はこちらも見てみると良いです

[Bug #17512] ostruct super regression

  • 以下のように super を経由して OpenStruct を参照した場合に Ruby 3.0 だと意図しない値が返ってきているというバグ報告
require 'ostruct'

class Foo < OpenStruct
  def foo
    super
  end
end

p Foo.new(foo: 123).foo
# Ruby 2.7 => 123
# Ruby 3.0 => nil
  • この問題は OpenStruct 0.3.2 で修正されている
  • 困っている人は個別に OpenStruct をインストールすると改善すると思います

[Misc #16436] hash missing #last method, make it not so consistent (it has #first)

  • Hash#first はあるけど Hash#last はないので一貫性がないよねーっていうチケット
homu = { id: 1, name: "homu", age: 14 }

pp homu.first
# => [:id, 1]

# error: undefined method `last' for {:id=>1, :name=>"homu", :age=>14}:Hash (NoMethodError)
pp homu.last
  • これは Hash#firstEnumerable#first で実装されており Enumerable#last がないからです
    • Enumerable#first があって Enumerable#last がないのは知らなかった
    • まあ確かにイテレーションを考えると #first はあっても #last がないのはなんとなくわかるよな…?
  • ちなみに以下のように #reverse_each を使うと終端の要素を取得することはできます
homu = { id: 1, name: "homu", age: 14 }

pp homu.reverse_each.first
# => [:age, 14]
  • このチケット自体には強い気持ちでの要望とかはないので、もし必要な人とかがいればコメントしてみるといいと思います

[Bug #17488] Regression in Ruby 3: Hash#key? is non-deterministic when argument uses DelegateClass

  • 次のように Ruby 3.0 で Hask#key?DelegateClass を渡すと意図しない結果が返ってくるというバグ報告
    • Ruby 3.0 では Hask#key? の結果が起動毎に変わることがある
puts "Running on Ruby: #{RUBY_DESCRIPTION}"

program = <<~EOS
  require "delegate"
  TypeName = DelegateClass(String)

  hash = {
    "Int" => true,
    "Float" => true,
    "String" => true,
    "Boolean" => true,
    "WidgetFilter" => true,
    "WidgetAggregation" => true,
    "WidgetEdge" => true,
    "WidgetSortOrder" => true,
    "WidgetGrouping" => true,
  }

  puts hash.key?(TypeName.new("WidgetAggregation"))
EOS

iterations = 20
results = iterations.times.map { `ruby -e '#{program}'`.chomp }.tally

# Ruby 3.0 で実行すると false が返ってくることがある
puts "Results of checking `Hash#key?` #{iterations} times: #{results.inspect}"
# Ruby 2.7 => Results of checking `Hash#key?` 20 times: {"true"=>20}
# Ruby 3.0 => Results of checking `Hash#key?` 20 times: {"false"=>12, "true"=>8}

[Bug #17481] Keyword arguments change value after calling super without arguments in Ruby 3.0

  • 次のように super を呼び出す前と後でキーワード引数の値が変わってしまうというバグ報告
class BaseTest
  def call(a:, b:, **)
  end
end

class Test < BaseTest
  def call(a:, b:, **options)
    p options  # =>  {:c=>{}}
    super
    # super を呼び出した後で options の値が変わってしまっている…
    p options  # =>  {:c=>{}, :a=>1, :b=>2}
  end
end

Test.new.call(a: 1, b: 2, c: {})

[Feature #17472] HashWithIndifferentAccess like Hash extension

  • ActiveSupport::HashWithIndifferentAccess をサポートする機能を Ruby で実装する?というチケット
  • ちょっとチケットの意図が読み取れてないので間違ってるかもしれませんが『Ruby 本体で ActiveSupport::HashWithIndifferentAccess を実装する(提供する)』というわけではなくて『 `ActiveSupport::HashWithIndifferentAccess を高速化できる仕組みを Ruby 側で用意する』っていうのが趣旨なのかな?
  • 具体的に言うと Ruby 3.0 で追加された Symbol#name みたいなのを Ruby 本体で ActiveSupport::HashWithIndifferentAccess を高速化できるような機能を提供する感じなんですかね
    • コメント読んでる限りこのあたりちょっと認識のズレがありそう
  • 最初読んだ時に『 ActiveSupport::HashWithIndifferentAccessRuby で提供するのはやめてくれ〜〜〜』と思っていたんですが機能提供って意味だとかなりよさそうですね

[RP] Remove deprecated URI.escape/URI.unescape