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

今週は { a: a, b: b }{ a:, b: } とかけるショートハンドが入りました。

[Feature #18168] Add ActiveSupport deep_transform_values to Ruby

require "active_support/all"

hash = { person: { name: 'Rob', age: '28' } }

# ネストした Hash すべてに value.to_s.upcase を適用させる
pp hash.deep_transform_values { |value| value.to_s.upcase }
# => {:person=>{:name=>"ROB", :age=>"28"}}
  • 再帰的に参照している場合は SystemStackError になっちゃうみたいですね
require "active_support/all"

hash = { a: 1 }
hash[:hash] = hash

# 再帰的に参照してる場合はエラー
# error: stack level too deep (SystemStackError)
pp hash.deep_transform_values{ |value| value.to_s.upcase }
  • チケットの内容が虚無なのでこのままだと流れていきそうな雰囲気

[Bug #18160] IndexError raised from MatchData#{offset,begin,end} does not keep the encoding of the argument

pp RUBY_VERSION  # => "3.0.2"

m = /.*/.match("foo")
m.offset("\u{3042}") rescue p $!.message
# => "undefined group name reference: \xE3\x81\x82"
  • これは最新版では修正されて正しい意図する文字コードで出力される
pp RUBY_VERSION  # => "3.1.0"

m = /.*/.match("foo")
m.offset("\u{3042}") rescue p $!.message
# => "undefined group name reference: あ"
  • 便利

[Feature #14579] Hash value omission

  • { x:, y: }{ x: x, y: y } のショートハンドにする提案
  • これ以外にも似たような提案は無限にされていたけど matz を説得できずに長年入っていなかった
  • 先日の RubyKaigi の感想戦でこれに関する議論がされて、matz を説得して無事に accepts された :tada:
  • これは以下のように x:x: x に展開するようなシンタックスシュガーになる
name = "homu"
age = 14

# { name: name, age: age } のシンタックスシュガー
{ name:, age: }
# => {:name=>"homu", :age=>14}

# 一部だけ値を割り当てることもできる
{ name:, age: 16 }
# => {:name=>"homu", :age=>16}


def tokyo
  "東京"
end

# メソッド呼び出しも可能
{ tokyo: }
# => {:tokyo=>"東京"}


def user(name:, age:)
  { name:, age: }
end

# user(name: name, age: age) になる
user(name:, age:)


# 現時点では定数も展開できる
X = 42
{ X: }
# => {:X=>42}
  • 現状は { X: } みたいな定数も展開されるがもしかしたらリリース時点でまた仕様が変わっている可能性があるので注意
  • ちなみにこれを利用すると if 変数に対して高速にアクセスする事ができる
def foo(if:)
  # if はキーワードなので変数にアクセスする時は Binding#local_variable_get を使う必要があった
  if_ = binding.local_variable_get(:if)

  # このショートハンドを使うとこうかけるようになる
  if_ = {if:}[:if]
end

[Feature #17355] Using same set of names in or-patterns (pattern matching with Foo(x) | Bar(x))

  • 以下のようにパターンマッチで束縛名が重複していると今はエラーになる
case [1, 2]
in [1, a] | [a, 3] then a
end
  • これをエラーではなくて各パターンごとで束縛できるようにする提案
  • これがあると例えば以下のようなコードが
def user_email(user)
  case user
  in User(email:) then email
  in Admin(email:) then email
  in Moderator(email:) then email
  end
end
  • 以下のように1つのパターンで定義する事ができる
def user_email(user)
  case user
  in User(email:) | Admin(email:) | Moderator(email:) then email
  end
end
  • 今はまだ実装中?らしい
  • これは普通にほしいなあ

[Misc #18150] Proposal: Deprecate leading zero syntax to declare octals, since it's extremely confusing (and Python 3 removed it too)

  • 8進数リテラルについて議論するチケット
  • 現状の Ruby0 が先頭に付いていると8進数として解釈される
    • 012 # => 10
  • また 0o が付いている場合も8進数として解釈される
    • 0o12 # => 10
  • なのでうっかり次のようなコードを書いてしまうとこれはエラーになってしまう
# "2021-09-01" のつもりで 09 と書いてしまった
# これはエラーになる
START_DATE = Date.new(2021, 09, 01)
# error: Invalid octal digit
  • このようなケースがあるので 0 を使った8進数リテラルRuby 3.x では非推奨にし、Ruby 4.0 では 0011 => # 11 にしよう、という提案
  • ちなみに先頭 0 が8進数リテラルになっている言語は JavaScript Go Java C がある
  • 逆にそうでない言語は RustElixir とのこと
  • また Python3+ はエラーになる
  • 個人的には 8進数リテラルを使ったことがないので気にはならないけど、既存のコードを非互換にしてまで入れる必要があるのかと言われると難しいところ

[Feature #18146] Add delete_prefix and delete_suffix to Pathname

  • Pathname#delete_prefixdelete_suffix を追加する提案
  • 元々は RuboCop が誤検知していたのが起因らしい
  • 誤検知を本体に実装しようとするアイディアがすごい(褒めてる)

[ruby/un] Add colorize command

  • 普通に便利そう

[Feature #16182] Should expr in a, b, c be allowed or not?

  • 元々は Ruby 2.7 時点での expr in [a, b, c] という1行 in の [] を省略できるようにするかどうかを議論するチケット
  • Ruby 2.7 (3.0) では []{} は省略できないようになっていた
# これらはシンタックスエラー
{ name: "homu" } in name:
[1, 2] in a, b
# OK
{ name: "homu" } in name:
[1, 2] in a, b
  • ちなみに右代入も許容されている
# OK
{ name: "homu" } => name:
[1, 2] => a, b
  • また、メソッドの引数に渡す場合は明示的に () を付ける必要があるので注意する
# これはエラー
puts([1] in String)

# () をつけると OK
puts(([1] in String))