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

今週は Numbered Parameters の _1it に置き換えるチケットがありました。

[Raises Exception for Range#last(n) with Float::INFINITY]

  • Range の終端が Float::INFINITY の場合に無限ループする
(6..Float::INFINITY).last(1)  # => infinite loop!!
(6..).last(1)   # cannot get the last element of endless range (RangeError)
(-Float::INFINITY..4).last(1) # can't iterate from Float (TypeError)
(..4).last(1)                 # can't iterate from Float (TypeError)
  • これを RangeError で例外にしようというチケット
  • そういえば昔 Range#first で似たようなチケット立てた記憶
  • このあたり、微妙に挙動が違っていて無限に難しい
  • 引数がない場合は以下のような挙動にもなる
p (1..10).last               # => 10
p (1..Float::INFINITY).last  # => Infinity
p (1..).last                 # Error: in `last': cannot get the last element of endless range (rangeerror)

[Feature #18159] Integrate functionality of syntax_suggest gem into Ruby

[Feature #18980] Re-reconsider numbered parameters: it as a default block parameter

  • _1 の変わりに it を Numbered parameters で利用したいというチケット
    • _1 は『未使用のローカル変数』に見えてしまうらしい
    • わたしは結構カジュアルに _1 を使っているんですが今の所名前に関して困ったとか混乱しとかはないですねえ(そういう話も聞いたことはない
    • 慣れの問題なんですかね?
  • どっちかって言うとナンパラ自体が特別な構文なので it のような一般的に使用されるような名前よりも _1 のように『特別な名前に見える方』が好みではありますね
  • チケット内でかなり議論が進んでいるので気になる人は読んでみてみるとよいかも
    • it じゃなくて @ を使おう、みたいな話もでてますね
    • it よりも @ の方が特別な意味があるように見えるので it よりはマシだと思うんですが @ にするぐらいなら _1 のままの方がいいかなあ…

[Feature #18951] Object#with to set and restore attributes around a block

  • 以下のように一時的に設定を変えたりインスタンス変数を変えたいときがある
def test_something_when_enabled
  # 一時的に SomeLibrary.enabled を有効にしたい
  enabled_was, SomeLibrary.enabled = SomeLibrary.enabled, true
  # test things
ensure
  SomeLibrary.enabled = enabled_was
end
def with_something_enabled
  # ブロック引数内では enabled_was を有効な状態で実行したい
  enabled_was = @enabled
  @enabled = true
  yield
ensure
  @enabled = enabled_was
end
  • この時に以下のような場合だと意図しない挙動になるケースがある
def test_something_when_enabled
  # some_call_that_may_raise で例外が発生した場合 SomeLibrary.enabled = nil になってしまう
  # これはまだ enabled_was に対して値が割り当てられていないため
  some_call_that_may_raise
  enabled_was, SomeLibrary.enabled = SomeLibrary.enabled, true
  # test things
ensure
  SomeLibrary.enabled = enabled_was
end
  • これを解決するために以下のような Object#with を導入したいというチケット
class Object
  def with(**attributes)
    old_values = {}
    attributes.each_key do |key|
      old_values[key] = public_send(key)
    end
    begin
      attributes.each do |key, value|
        public_send("#{key}=", value)
      end
      yield
    ensure
      old_values.each do |key, old_value|
        public_send("#{key}=", old_value)
      end
    end
  end
end

def test_something_when_enabled
  SomeLibrary.with(enabled: true) do
    # test things
  end
end

GC.with(measure_total_time: true, auto_compact: false) do
  # do something
end
  • こういう制御は結構するので便利そう