Ruby 2.7がEOLとなるので、多くのRubyバージョンが3.0以上になっていることでしょう。
Qiita開発メンバーもRuby 3.0以降の機能について勉強会をしており、今回はRuby3.0で変更された1行パターンマッチと一行メソッド定義についてまとめました。
1行パターンマッチ
パターンマッチはRuby2.7から導入されており、1行パターンマッチ自体も2.7から使うことはできました。
{ hoge: 'fugafuga' } in { hoge: fuga }
# fuga == 'fugafuga'
{ hoge: 'hogehoge' } in { hoge: }
# hoge == 'hogehoge'
これがin
だけではなく、=>
を使うことができるようになり、右代入のように使うことができるようになりました。
この記法はRuby3.0でexperimentalとして導入され、3.1でexperimentalが外れました。
{ key: 'value' } => hash
# hash = { key: 'value' }
0 => a
# a = 0
右代入について
Redmineチケット: Feature #15921: R-assign (rightward-assignment) operator - Ruby master - Ruby Issue Tracking System
以前から「右代入」という言葉は耳にしたことがありましたが、実はかなり昔から議論がされていたようです。
右代入を導入したモチベーション等はこのようにあります。
Rubyにおける変数の特徴として、代入が宣言を兼ねていることが挙げられます。この特徴があることで、変数代入の右辺には変数と同じ名前のメソッドを書けないとか、if修飾子の中で宣言・代入したものは、修飾子の左側では使えないなどの制限が出てくるんですよ。
さらに、この仕様になっているために、長いメソッドチェーンがある場合などに視線の動きが右方向に移動した後、(変数名を確認するために)目線が先頭に戻らなければならない。読み書きする際に負担になるという意見も挙げられていました。
解決策として右方向への代入機能がほしいという議論は昔からされていたんです。おそらく話題としては10年くらい前からありましたね。
上記のツイートのスレッドを見ると、COBOLやRにも右代入があるらしいです。
メリットと注意点
Redmineのチケット等によると、以下のような点が挙げられます。
rails cとかで長いメソッドチェーンを打ったときにそのまま使える
foo.method1.method2.method3.method4.method5 => hoge
先頭が揃えられる
if true
'hoge'
else
'fuga'
end => val
# val = 'hoge'
foo.method1
.method2
.method3
.method4
.method5 => hoge
注意点
- 比較演算子と少し紛らわしい
-
0 >= a
vs0 => a
-
- ハッシュ、キーワード引数と紛らわしい
-
method1 a => b
はmethod1(a => b)
となってしまう - 右代入にする場合は
method(a) => b
と書く必要がある
-
- 返り値を使えない1
ret = ({ a: 1, b: 2, c: 3 } => hash)
- ローカル変数のみに使えて、インスタンス変数、グローバル変数などには使えない1
{ a: 1, b: 2, c: 3 } => @a # SyntaxError
(プロと読み解く Ruby 3.0 NEWS - クックパッド開発者ブログから一部引用)
1行パターンマッチの色々な使い方
マッチしないとNoMatchingPatternErrorが発生します。
3.1からカッコ省略が可能となります。( {}
, []
)
hash = { a: 1, b: 2, c: 3 }
hash => a:, b:, c:
# a = 1
# b = 2
# c = 3
arr = [1, 2, 3]
arr => a, b, c # これは右代入の必要はない
# a = 1
# b = 2
# c = 3
['foo', 'bar', 'baz'] => _, _, val
ハッシュは一部のみのマッチでも使えます。
json = '{"status": "error", "value": "", "messages": ["foo", "bar"]}'
JSON.parse(json, symbolize_names: true) => status:, messages: [first_message, *] # valueはマッチさせていない
# status = "error"
# messages = ["foo", "bar"]
# first_message = "foo"
配列は要素数も一致している必要があるので、NoMatchingPatternErrorに注意です。
# 通常の代入だとエラーは起きない
_, val = ['foo', 'bar', 'baz'] # val == 'bar'
# パターンマッチだとエラー
['foo', 'bar', 'baz'] => _, val # NoMatchingPatternError
['foo', 'bar', 'baz'] => _, *val # val == ['bar', 'baz']
Ruby 3.2でFindパターンが正式に利用できるので、以下のようにも使える
ary = [1, 2, 3, 4, 5]
ary => [*, 3, x, *]
# x == 4
Ruby 3.2でTimeもパターンマッチ可能になりました。
Time.now => year:, month:, day:, hour:, min:, sec:, zone:
DateTime.new(2023, 2, 26, 23, 50) => datetime
datetime => year:, month:, day:, hour:, min:, sec:
一行メソッド定義(エンドレスメソッド)
Endohさんによるエイプリルフールネタが始まりらしいです。
Redmineチケット: Feature #16746: Endless method definition - Ruby master - Ruby Issue Tracking System
Ruby syntax is full of "end"s. I'm paranoid that the ends end Ruby. I hope Ruby is endless.
記法は以下のようになっています。
def value(args) = expression
具体例として、Pull Requestに記載されているものを引用します。(Endless method definition [Feature #16746] by nobu · Pull Request #2996 · ruby/ruby)
def hello(name) =
puts("Hello, #{ name }")
hello("endless Ruby") #=> Hello, endless Ruby
def inc(x) = x + 1
p inc(42) #=> 43
x = Object.new
def x.foo = "FOO"
p x.foo #=> "FOO"
def fib(x) =
x < 2 ? x : fib(x-1) + fib(x-2)
p fib(10) #=> 55
ちなみに、チケットに記載されていますが、エンドレスメソッドの対象になる以下のようなコードは、ruby/rubyのコードベースで24%を占めていたらしいです。
# three-line style
def value
@val
end
# one-line style
def value; @val; end
def hoge = 'hoge'
注意点
セッターメソッドはエラーになります。
def set=(x) = @x = x
#=> setter method cannot be defined in an endless method definition
defと同じスコープのローカル変数へはアクセスできないです。
hoge = 'hoge'
def hoge_length = hoge.length
#=> undefined local variable or method `hoge' for main:Object (NameError)
感想
右代入もエンドレスメソッドも、使いどころはありそうですが、ルールなしにプロダクションのコードに混在すると混乱しそうだなと感じました。
Qiita開発メンバーの中でも、どういうタイミングで使うべきかを議論しましたが、各チームごとに使い所を話し合ったほうがいいのかもと感じました。
参考