2020/12/24 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[Bug #17030] Enumerable#grep{_v} should be optimized for Regexp その後
- 以前紹介した
ary.select { |e| e.match?(reg) }
と比較してary.grep(reg)
の方が遅いので最適化しよう、という提案 - その後、議論が進んで最終的に
ary.grep(reg)
のようにブロック引数がない場合はMatchData
を生成しないように対応された
ary = ["homu", "mami", "mado"] reg = /.*/ ary.grep(reg) # or Regexp.last_match p $~ # 2.7 => #<MatchData "mado"> # 3.0 => nil # ブロックを渡した場合は MatchData を生成する ary.grep(reg) {} # or Regexp.last_match p $~ # 2.7 も 3.0 => #<MatchData "mado">
- この挙動は非互換な変更になるので注意する必要がります
[PR #124] Add measure command
irb
にmeasure
というコマンドが追加された- これは
measure
を呼び出した以降で実行時間を出力するような事を行う
irb(main):001:0> 3 => 3 irb(main):002:0> measure TIME is added. => nil irb(main):003:0> 3 processing time: 0.000058s => 3 irb(main):004:0> measure :off => nil irb(main):005:0> 3 => 3
- また以下のようにカスタマイズすることも可能です
IRB.conf[:MEASURE_PROC][:CUSTOM] = proc { |context, code, line_no, &block| time = Time.now result = block.() now = Time.now puts 'custom processing time: %fs' % (Time.now - time) if IRB.conf[:MEASURE] result }
- これは普通に便利そう
[Bug #17428] Method#inspect bad output for class methods
Method#inspect
の内容がおかしいというバグ報告- 次のようにクラス名が表示されないケースがある
- おそらくこれの影響
p String.method(:prepend) # 2.7 => #<Method: String.prepend(*)> # 3.0 => #<Method: #<Class:Object>(Module)#prepend(*)>
- この問題は最新版では以下のように修正された
p String.method(:prepend) # 2.7 => #<Method: String.prepend(*)> # 3.0 => #<Method: #<Class:String>(Module)#prepend(*)>
- これは気づかなかった…
[Feature #17116] raise ArgumentError in Enumerator#new in no given blocks
Enumerator.new(obj)
みたいに.new
にブロックを渡さない場合は deprecated warning が出ている- この警告がでているのは Ruby 2.0 の頃から
obj = Object.new # warning: Enumerator.new without a block is deprecated; use Object#to_enum instead Enumerator.new(obj)
- これはもうエラーにしてしまってもいいんじゃないか、というチケット
- これはマージされて Ruby 3.0 からはエラーになるので注意しましょう
[Bug #17423] Prepend
should prepend a module before the class
- Ruby 3.0 では以下のように
prepend
の挙動が変わってしまう事がある
module M; end module A; end class B; include A; end A.prepend M B.prepend M # B.prepend M してるのに M が B よりもあとに来る p B.ancestors # 2.7 => [M, B, A, Object, Kernel, BasicObject] # 3.0 => [B, M, A, Object, Kernel, BasicObject]
- これを次のように
M
が重複するようにするという内容のチケット
module M; end module A; end class B; include A; end A.prepend M B.prepend M # このように修正する p B.ancestors # => [M, B, M, A, Object, Kernel, BasicObject]
- この問題は Ruby 3.0 リリース後に対応される予定です
- ちなみにこのチケットはまつもとさん自身が建てています
[Feature #17411] Allow expressions in pattern matching
- パターンマッチでは次のようにパターンの部分に式を書くことができません
user = { name: "homu", age: 14 } case user # syntax error, unexpected '+', expecting ')' # パターンに式を記述する事ができない in { age: (7 + 7) } end
- これを
^(expression)
のようにかけるようにしようという提案
user = { name: "homu", age: 14 } case user # OK # 式を書く場合は ^() を使う in { age: ^(7 + 7) } end
- この実装自体はすでにされていますが流石に Ruby 3.1 以降で入りそうな感じです
[Bug #17398] SyntaxError in endless method
# OK def hoge = puts("homu") # syntax error, unexpected string literal, expecting `do' or '{' or '(' def hoge = puts "homu"
【一人 bugs.ruby Advent Calendar 2020】[Bug #17423] `Prepend` should prepend a module before the class【24日目】
一人 bugs.ruby Advent Calendar 2020 24日目の記事になります。
[Bug #17423] Prepend
should prepend a module before the class
このブログでも何回か紹介しているんですが Ruby 3.0 では Module#include / #prepend
の挙動がちょっと変わります。
特に以下のように Ruby 2.7 と Ruby 3.0 でかなり挙動が変わるコードも存在しています。
module M; end module A; end class B; include A; end A.prepend M B.prepend M # B.prepend M してるのに M が B よりもあとに来る p B.ancestors # 2.7 => [M, B, A, Object, Kernel, BasicObject] # 3.0 => [B, M, A, Object, Kernel, BasicObject]
このチケットはこの問題を解決するために以下のように M
が重複することを許容しよう、という旨のチケットになります。
module M; end module A; end class B; include A; end A.prepend M B.prepend M # このように修正する p B.ancestors # => [M, B, M, A, Object, Kernel, BasicObject]
これはこれでちょっと気持ち悪い気もするんですがまあ無いよりはマシですかねえ…ぶっ壊れるときはどっちにしてもぶっ壊れそうだし…。
この問題は Ruby 3.0 リリース後に対応される予定となっています。
ちなみにこのチケットはまつもとさん自身が建てていたりします。
【一人 bugs.ruby Advent Calendar 2020】[Feature #16986] Anonymous Struct literal【23日目】
【一人 bugs.ruby Advent Calendar 2020】[Feature #16986] Anonymous Struct literal【23日目】 一人 bugs.ruby Advent Calendar 2020 23日目の記事になります。
[Feature #16986] Anonymous Struct literal
これは Struct.new(:a, :b).new(1, 2)
を ${ a: 1, b: 2 }
のようなリテラルで定義できるようにするチケットです。
s = ${a: 1, b: 2, c: 3} s.a # => 1 s.b # => 2 s.c # => 3
いまはそんなに Struct
は使わないけどこういう記法があるとガンガン使いそうですねー。
例えばこんな感じに雑にダックタイピング呼び出しするメソッドに値を渡す場合とか?
def print(user) pp "#{user.id} #{user.name}" end name = "homu" age = 14 # Struct を経由してメソッド呼び出しされるようにする print(${ name: name, age: age })
Struct
だと obj.value
だけじゃなくて obj[:value]
みたいに添え字アクセスもできるので Hash の代わりとして使用できそうですね。
Hash と違い存在しないキーにアクセスするとエラーになるのは便利そう
# Hash の場合は typo してても気づきづらい user = { name: "homu", age: 14 } # no error user[:nmae] # Struct だと存在しないキーにアクセスするとエラーになる user = Struct.new(:name, :age).new("homu", 14) # error user[:nmae]
あとは ${}
だとブロックと差別化できるので
p { a: 1, b: 2 }
とは書けないんですが p ${ a: 1, b: 2 }
はかける的な。
関係ないけどどういう経緯でチケットが建てられたのか見ているとちょっとおもしろいです
Struct
に関してはこちらのスライドも参照してください。
[今更聞けない! Struct の使い方と今後の可能性について]
【一人 bugs.ruby Advent Calendar 2020】[Feature #17004] Provide a way for methods to omit their return value【22日目】
一人 bugs.ruby Advent Calendar 2020 22日目の記事になります。
[Feature #17004] Provide a way for methods to omit their return value
このチケットは任意のメソッドが戻り値を受け取るか受け取らないかを判定するメソッドの追加を追加するという内容のチケットです。
ということかというと RubyVM.return_value_is_used?
というメソッドを追加し、これをメソッド内で呼び出すと
- 戻り値を受け取る場合:
true
を返す - そうでない場合:
false
を返す
というような判定を行うことができます。
具体的に言うとこんな感じで判定する事ができます。
def hoge if RubyVM.return_value_is_used? pp "戻り値を受け取る" else pp "戻り値を受け取らない" end end hoge # "戻り値を受け取らない" value = hoge # "戻り値を受け取る" Array hoge # "戻り値を受け取る" hoge.nil? # "戻り値を受け取る" # 最後に呼び出したやつも? hoge # "戻り値を受け取る"
この判定メソッドを利用すると次のように『戻り値を受け取らない場合は無駄な処理を省く』事ができます。
class Hash def refresh(key) # 引数を受け取る場合のみ result を設定する if RubyVM.return_value_is_used? result = self[key] end self[key] = nil result end end homu = { name: "homu", age: 14 } homu.refresh(:name) p homu # => {:name=>nil, :age=>14} age = homu.refresh(:age) pp homu # => {:name=>nil, :age=>nil} pp age # => 14
class User < ActiveRecord::Base def update_name(name) update!(name: name) # reload した値を返す reload if RubyVM.return_value_is_used? end end
これは面白いアプローチですね。
ただし、次のように戻り値になる場合は『戻り値を受け取る』ことになるので注意。
def hoge if RubyVM.return_value_is_used? pp "戻り値を受け取る" else pp "戻り値を受け取らない" end end def foo hoge end foo # "戻り値を受け取る" def bar hoge nil end bar # "戻り値を受け取らない"
便利そうっちゃ便利そうだけどメソッドごとに RubyVM.return_value_is_used?
で処理を分岐するのはめっちゃきつそう…。
実際には極端に重くなるようなメソッドぐらいで使いそうな気がするけど…どうだろうか。
参照
【Ruby 3.0 Advent Calendar 2020】Ruby 3.0 で WEBrick と SDBM が標準ライブラリから削除される【22日目】
Ruby 3.0 Advent Calendar 2020 22日目の記事になります。
この前の銀座Rails で完全に話忘れてたのでここに書いておきます。
Ruby 3.0 で WEBrick と SDBM が標準ライブラリから削除される
表題のとおりですが Ruby 3.0 から標準ライブラリの WEBrick と SDBM が削除されます。
- [Bug #8446] sdbm fails to fetch existing key if many elements in it
- [Feature #17303] Remove webrick from stdlib
どちらも削除される主な理由としてはサポートするのが難しくなってきたからとのことです。
特に WEBrick は最近脆弱性の問題もありました。
今後は ruby/sdbm と ruby/webrick でメンテされていくようです。
SDBM や WEBrick を使用したい場合は明示的に gem install
したり Gemfile
に記述する必要があります。
この影響に対してすでに Rails は対処済みです。
【一人 bugs.ruby Advent Calendar 2020】[Feature #17292] id outputed by inspect and to_s output does not allow to find actual object_id and vice-versa【21日目】
一人 bugs.ruby Advent Calendar 2020 21日目の記事になります。
[Misc #17199] id outputed by inspect and to_s output does not allow to find actual object_id and vice-versa
Ruby 2.7 から #inspect
( #to_s
) と #__id__
が返すアドレスの関連性が一致しなくなったという報告チケットなります。
どういうことかというと Ruby 2.7 から以下のように #__id__
が返す値が変わりました。
obj = Object.new # Ruby 2.7 から #__id__ の値が変わった p "#__id__=#{obj.__id__}" # Ruby 2.6 => "#__id__=47458875463160" # Ruby 2.7 => "#__id__=60"
これと #inspect
がどう関係あるのかというと #__id__
から #inspect
で表示されるアドレスを推論できていたんですが、それが上記の変更でできなくなりました。
obj = Object.new p "#inspect=#{obj.inspect}" # => "#inspect=#<Object:0x00005653c2d3abf0>" # Ruby 2.6 では __id__ の結果をシフトすると inspect に表示される id と同じになっていた # しかし Ruby 2.7 ではできなくなっている p "shifted_id=#{(obj.__id__ << 1).to_s(16)}" # Ruby 2.6 => "shifted_id=5653c2d3abf0" # Ruby 2.7 => "shifted_id=78"
これにより何が起きるのかというと例えば #inspect
の出力の 0x00005653c2d3abf0
というアドレスから __id__
を推論する事ができたので次のように ObjectSpace._id2ref
で実際のオブジェクトの値を取得する事ができていました。
しかし、 Ruby 2.7 の変更によりこれはできなりました。
o = Object.new # 任意の __id__ からそのオブジェクトが取得できる pp ObjectSpace._id2ref(o.__id__) # inspect から id を取得してそれを元にしてオブジェクトを取得する事ができた # これが Ruby 2.7 からは動作しなくなっている id_from_inspect = o.inspect[/#<Object:(.*)>/, 1].to_i(16) pp ObjectSpace._id2ref(id_from_inspect >> 1)
こんな仕様があったんですね…。
例えば Object#inspect
をログ出力しているような場合にその出力されたアドレスから『実際のオブジェクトを参照したい』みたいな場合に利用できるのかなあ、とは思いました。
【一人 bugs.ruby Advent Calendar 2020】[Bug #17017] Range#max & Range#minmax incorrectly use Float end as max【20日目】
一人 bugs.ruby Advent Calendar 2020 20日目の記事になります。
誤解しないように最初に書いておくとこのチケットによる Ruby 3.0 への影響はありません。
[Bug #17017] Range#max & Range#minmax incorrectly use Float end as max
このチケットは以下のように range.max
と range.to_a.max
で差異があり一貫性がないので対処しよう、という旨のチケットです。
# # これは期待する挙動 (1..3.1).to_a == [1, 2, 3] # to_a を経由した場合の結果 (1..3.1).to_a.max == 3 (1..3.1).to_a.minmax == [1, 3] # Range#max Range#minmax (1..3.1).max == 3.1 (1..3.1).minmax == [1, 3.1]
これは最初は以下のように range.max == range.to_a.max
となるように修正されました。
(1..3.1).max # => 3 (1..3.1).minmax # => [1, 3]
Range#max
の変更により既存のコードが壊れた
Range#max
の戻り値を変えたことにより以下のような非互換な挙動が発生していました。
NOTE: ちなみに 2.8.0 というのは当時はまだバージョニングが 3.0 ではなかった名残です。
# 2.7.1 : Infinity を返す # 2.8.0dev : error: `floor': Infinity (FloatDomainError) p (42..Float::INFINITY).max # 明示的に Float に変換すると OK p (42.to_f..Float::INFINITY).max
この非互換な変更により RuboCop や Rails が壊れたという報告がされました。
Float::INFINITY
の場合に非互換にしないようにした
先程の (42..Float::INFINITY).max
が非互換な挙動になってしまう為、非互換にならないような対応がなされました。
p (42..Float::INFINITY).max # 2.7.1 : => Infinity # 2.8.0dev : => Infinity
これにより件の非互換な変更に対する問題は対処されました。
最終的には全て Revert され変更はなくなった
さて、いろいろと紆余曲折があったこのチケットですが最終的にはまつもとさんの意向により全て Revert されました。
意図としては、
Range
の役割としては『Enumerable
としての機能』と『両端のデータを持つオブジェクトとしての機能』の 2パターンがある- 今回の
#max
#minmax
は『両端のデータを返す』というのが期待する挙動となる - なので
(1..3.5).max
は終端の3.5
を返すのは意図する動作になる
とのことでした。
長々と議論されたチケットですがこのチケットによる Ruby 3.0 へ影響はありません。
Ruby はこんな感じで紆余曲折あり開発されているというのがわかるチケットでした。