Ruby 3.4: Rangeがイテレート不可の場合にRange#sizeがエラーにならない問題を修正(翻訳)
RubyのRange
は値の範囲を表すオブジェクトで、開始と終了という2つの値が定義されます。Range
は基本的なデータ構造の一種であり、2つの値には数値やアルファベットに加えて日付も指定可能です。Range
は、値のシーケンスを表すのにも、値の範囲を表すのにも使えます。
範囲は、以下のように2通りの方法で指定できます。
- 1:
..
を用いる"inclusive"な方法
(開始値..終了値)
とすると、終了値が範囲に含まれます。
inclusive_range = (1..5)
#=> [1, 2, 3, 4, 5]
- 2:
...
を用いる"exclusive"な方法
(開始値...終了値)
とすると、終了値は範囲に含まれません。
# `...`を使うと1, 2, 3, 4は含まれるが5は含まれない
exclusive_range = (1...5)
#=> [1, 2, 3, 4]
🔗 RubyのRange#size
メソッド
Range#size
メソッドは、その範囲に含まれる要素の個数を返します。開始値と終了値はどちらもNumeric
でなければならず、それ以外の場合はnil
を返します。
(10..20).size #=> 11
(10...20).size #=> 10
('a'..'z').size #=> nil
🔗 修正前
Range
の開始値がFloat
またはRational
の場合、「要素の個数」という概念は無意味です。そのようなRange
は「2の次は3、3の次は4...」のようなイテレートができないので、配列のような「要素」は存在しようがないことがわかります。
それにもかかわらず、開始値がFloat
またはRational
のRange
は、Range#size
でInteger
を返してしまいます。さらに、(..終了値)
のようなbeginlessの場合は、Range#size
でInfinity
を返してしまいます。
(0.51..5).size #=> 5
(0.51..5.quo(2)).size #=> 2
(5.quo(2)..10).size #=> 8
(..1).size #=> Infinity
どうやら、Range
の開始値と終了値を両方ともNumeric#round
で直近の整数値に丸めてから、Range#size
が計算されていたようです。
🔗 修正後
Ruby 3.4からは、Range
がイテレート不可の場合はRange#size
でTypeErrorを発生するよう修正されました(#8663)。
(0.51..5).size #=> can't iterate from Float (TypeError)
(0.51..5.quo(2)).size #=> can't iterate from Float (TypeError)
(5.quo(2)..10).size #=> can't iterate from Rational (TypeError)
(..1).size #=> can't iterate from NilClass (TypeError)
概要
元サイトの許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。
読みやすさのためコードブロックの表記を訳文で変更しています。