Ruby 2.7 の Numbered parameters を試す

Ruby の trunk に Numbered parameters なる新機能が入ったようなのでどんな挙動をするのか触ってみた。

動作確認に使っているバージョンはこちら

$ ruby -v
ruby 2.7.0dev (2019-03-18 trunk 67294) [x86_64-darwin17]

基本的な使い方

まずは基本。ブロックに渡された引数を @1, @2 のように参照できる。

def call_block(*args)
  yield *args
end

call_block(:a, :b) { puts "(#{@1} #{@2})" } # => (a, b)

引数が足りていない場合、nil になる。

call_block(:a) { puts "(#{@1} #{@2})" } # => (a, )

引数が1つでそれが配列だと展開される。これは気をつけた方が良さそう。

call_block([:a, :b]) { puts "(#{@1} #{@2})" } # => a b

引数が2つ以上なら配列は展開されない模様。

call_block([:a, :b], :c) { puts "(#{@1}, #{@2}, #{@3})" } # => ([:a, :b], c, )

キーワード引数は一番後ろの引数にまとめられる。

def call_with_kwargs
  yield :a, :b, x: 1, y: 2
end

call_with_kwargs { puts "(#{@1}, #{@2}, #{@3})" } # => (a, b, {:x=>1, :y=>2})

エラーになる使い方

通常のブロック引数を宣言している場合、シンタックスエラーになる。

call_block(:a) { |x| puts "#{@1}" } # => ordinary parameter is defined

もちろんブロックの外で参照するとシンタックスエラーになる。

puts @1 # => numbered parameter outside block

Numbered parameters への代入もシンタックスエラーになる。

proc { @1 = 42 } # => Can't assign to numbered parameter @1

(滅多にないだろうが)ブロックを受け取るブロックでブロックを参照する方法がわからなかった。

# これはできる
call_block = proc { |&b| b.call(1) }
call_block.call { |x| puts x } # => 1

# これはできない
call_block = proc { @1.call(1) }
call_block.call { |x| puts x } # => undefined method `call' for nil:NilClass (NoMethodError)

Proc の中身を見てみる

Proc#parameters で Numbered parameters がどのように見れるか確認する。

p proc { |x, y| x + y }.parameters # => [[:opt, :x], [:opt, :y]]
p proc { @1 + @2 }.parameters # => [[:opt, nil], [:opt, nil]]

Numbered parameters は名前が無いオプショナル引数と認識されているようだ。

所感

正規表現では Named capture 使う派としてはパッと見だけだと「どうなの?」と思ってしまうが、やっぱりちょっとした eachmap で欲しくなるのは目に見えているので、なんだかんだ言ってあれば便利に使うと思う。

Numbered parameters だけだと可読性が下がるのは目に見えているので、基準として式1つのみの1行ブロックに限定して使うくらいがよさそう。