2022/05/20 今回の気になった bugs.ruby のチケット
今週はパターンマッチの #deconstruct
を拡張する提案がありました。
[Feature #18788] Support passing Regexp options as String to Regexp.new
Regexp.new
の第二引数にオプションを渡すことができる
Regexp.new('foo', Regexp::IGNORECASE | Regexp::MULTILINE | Regexp::EXTENDED) # => /foo/imx
- これを文字列を渡せるようにする提案
Regexp.new('foo', 'i') # => /foo/i Regexp.new('foo', :i) # => /foo/i Regexp.new('foo', 'imx') # => /foo/imx Regexp.new('foo', :imx) # => /foo/imx
- 実際のユースケースは限られているがまったくないわけではない
- https://bugs.ruby-lang.org/issues/18788#note-3
- rubocop : https://github.com/rubocop/rubocop-ast/blob/816dfe7f2ca4e92c7eda226a9e8b44aa9fa81e81/lib/rubocop/ast/node_pattern/lexer.rb#L21-L58
- parser : https://github.com/whitequark/parser/blob/09d681e534885f1aa22f0099089841ae9d86f847/lib/parser/builders/default.rb#L2224-L2242
- ちなみに
Regexp::IGNORECASE
Regexp::MULTILINE
Regexp::EXTENDED
以外の値を渡すとRegexp::IGNORECASE
になる- これも混乱するので対応する理由として上げられている
Regexp.new('foo', Regexp::IGNORECASE) # => /foo/i # これも i になる Regexp.new('foo', "hoge") # => /foo/i # i を文字列で渡せるように見えるが Regexp.new('foo', "i") # => /foo/i # これも i になる Regexp.new('foo', "m") # => /foo/i
[Feature #14602] Version of dig that raises error if a key is not present
- 以下のように要素が見つからなかった場合に例外が発生する
#dig!
を追加する提案
hash = { :name => { :first => "Ariel", :last => "Caplan" } } hash.dig!(:name, :first) # => Ariel hash.dig!(:name, :middle) # raises KeyError (key not found: :middle) hash.dig!(:name, :first, :foo) # raises TypeError (String does not have #dig! method)
#[]
でも似たような事は実現できるがより自明にしたいのが目的
hash = { :name => { :first => "Ariel", :last => "Caplan" } } hash[:name][:first] # => Ariel hash[:name][:middle] # => nil hash[:name][:first][:foo] # => `[]': no implicit conversion of Symbol into Integer (TypeError)
[Feature #18774] Add Queue#pop(timeout:)
Thread::Queue#pop
の引数にtimeout:
を追加する提案- 以前から提案はあったけどタイムアウトしたときにどうなるのかの議論で止まっていたらしい
- 例外を発生させるのか、
nil
を返すのか - https://bugs.ruby-lang.org/issues/18774#note-1
- 例外を発生させるのか、
Thread::Queue#pop(timeout:, exception: true/false/class)
のようにexception:
を追加するコメントもある
[Feature #18773] deconstruct to receive a range
#deconstruct_keys
を定義する事でパターンマッチで{ a:, b: }
パターンを任意のオブジェクトで使うことができる- 引数に
Hash
のキーを受け取る事ができる
- 引数に
class Time # Hash パターンのキーを受け取る # in { year:, day: } なら [:year, :day] def deconstruct_keys(keys) # キーを元にしてパターンマッチに必要な Hash を返す keys.to_h { [_1, send(_1)] } end end time = Time.new(2020, 1, 1) pp time # time.year と time.day を受け取る事ができる case time in { year:, day: } pp year pp day end
- 同様に
[a, b]
の場合は#deconstruct
で拡張できる - この引数に
[]
の個数をRange
で受け取る提案
class DeconstructWithRange def initialize(values) @values = values end # range で in の配列の個数を 個数..個数 で受け取る # * がふくまれている場合は 個数..無限 になる def deconstruct(range) range.cover?(@values.length) ? @values : [] end end case DeconstructWithRange.new([1, 2]) # deconstruct(2..2) を呼び出す in ["hoge", "foo"] true # deconstruct(3..3) を呼び出す in ["hoge", "foo", "bar"] true # deconstruct(2..) を呼び出す in ["hoge", "foo", "bar", *] true else true end
- ユースケースとしては以下のようなコードが上げられている
class ActiveRecord::Relation def deconstruct(range) # 配列の個数が一致している時のみ record を読み込んでくる (loaded? || range.cover?(count)) ? records : nil end end case Person.all in [] "No records" # Person.all のレコード数が1個の時のみレコードを読み込んで処理する in [person] "Only #{person.name}" else "Multiple people" end
- あれば便利そうな気がするけどわざわざ
Range
で受け取らなくてもよい気がするなあ- 個数 +
*
があるかどうか、の2つの情報を受け取るほうが意図は伝わりやすそう?
- 個数 +
class ActiveRecord::Relation # 個数と * があるかどうかを受け取る def deconstruct(count, rest:) # 配列の個数が一致している時のみ record を読み込んでくる (loaded? || self.count == count) ? records : nil end end