Ruby3の1行パターンマッチ（右代入）と一行メソッド定義（エンドレスメソッド）について

Ruby 2.7がEOLとなるので、多くのRubyバージョンが3.0以上になっていることでしょう。
Qiita開発メンバーもRuby 3.0以降の機能について勉強会をしており、今回はRuby3.0で変更された1行パターンマッチと一行メソッド定義についてまとめました。

https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/

## 1行パターンマッチ

パターンマッチはRuby2.7から導入されており、1行パターンマッチ自体も2.7から使うことはできました。

https://speakerdeck.com/k_tsj/pattern-matching-new-feature-in-ruby-2-dot-7

https://qiita.com/jnchito/items/9bb4aa1dcefa00257815

```ruby
{ hoge: 'fugafuga' } in { hoge: fuga }
# fuga == 'fugafuga'

{ hoge: 'hogehoge' } in { hoge: }
# hoge == 'hogehoge'
```

これが`in`だけではなく、`=>`を使うことができるようになり、右代入のように使うことができるようになりました。
この記法はRuby3.0でexperimentalとして導入され、3.1でexperimentalが外れました。

```ruby
{ key: 'value' } => hash
# hash = { key: 'value' }

0 => a
# a = 0
```

### 右代入について

Redmineチケット: [Feature #15921: R-assign (rightward-assignment) operator - Ruby master - Ruby Issue Tracking System](https://bugs.ruby-lang.org/issues/15921)

以前から「右代入」という言葉は耳にしたことがありましたが、実はかなり昔から議論がされていたようです。
右代入を導入したモチベーション等はこのようにあります。

https://hackerslab.aktsk.jp/2021/03/04/122751

> Rubyにおける変数の特徴として、代入が宣言を兼ねていることが挙げられます。この特徴があることで、変数代入の右辺には変数と同じ名前のメソッドを書けないとか、if修飾子の中で宣言・代入したものは、修飾子の左側では使えないなどの制限が出てくるんですよ。
> 
> さらに、この仕様になっているために、長いメソッドチェーンがある場合などに視線の動きが右方向に移動した後、（変数名を確認するために）目線が先頭に戻らなければならない。読み書きする際に負担になるという意見も挙げられていました。
> 
> 解決策として右方向への代入機能がほしいという議論は昔からされていたんです。おそらく話題としては10年くらい前からありましたね。

https://twitter.com/yukihiro_matz/status/903387184807292929?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E903387

上記のツイートのスレッドを見ると、COBOLやRにも右代入があるらしいです。

### メリットと注意点

Redmineのチケット等によると、以下のような点が挙げられます。

rails cとかで長いメソッドチェーンを打ったときにそのまま使える

```ruby
foo.method1.method2.method3.method4.method5 => hoge
```

先頭が揃えられる

```ruby
if true
  'hoge'
else
  'fuga'
end => val
# val = 'hoge'

foo.method1
  .method2
  .method3
  .method4
  .method5 => hoge
```

#### 注意点

- 比較演算子と少し紛らわしい
    - `0 >= a` vs `0 => 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 - クックパッド開発者ブログ](https://techlife.cookpad.com/entry/2020/12/25/155741)から一部引用)
[^1]: [プロと読み解く Ruby 3.0 NEWS - クックパッド開発者ブログ](https://techlife.cookpad.com/entry/2020/12/25/155741)

### 1行パターンマッチの色々な使い方

マッチしないとNoMatchingPatternErrorが発生します。
3.1からカッコ省略が可能となります。（ `{}`, `[]`）

```ruby
hash = { a: 1, b: 2, c: 3 }
hash => a:, b:, c: 
# a = 1
# b = 2
# c = 3
```

```ruby
arr = [1, 2, 3]
arr => a, b, c # これは右代入の必要はない
# a = 1
# b = 2
# c = 3

['foo', 'bar', 'baz'] => _, _, val
```

ハッシュは一部のみのマッチでも使えます。

```ruby
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に注意です。

```ruby
# 通常の代入だとエラーは起きない
_, val = ['foo', 'bar', 'baz'] # val == 'bar'

# パターンマッチだとエラー
['foo', 'bar', 'baz'] => _, val # NoMatchingPatternError
['foo', 'bar', 'baz'] => _, *val # val == ['bar', 'baz']
```

Ruby 3.2でFindパターンが正式に利用できるので、以下のようにも使える

```ruby
ary = [1, 2, 3, 4, 5]
ary => [*, 3, x, *]
# x == 4
```

Ruby 3.2でTimeもパターンマッチ可能になりました。

```ruby
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](https://bugs.ruby-lang.org/issues/16746)

> Ruby syntax is full of "end"s. I'm paranoid that the ends end Ruby. I hope Ruby is endless.

記法は以下のようになっています。

```ruby
def value(args) = expression
```

具体例として、Pull Requestに記載されているものを引用します。（[Endless method definition [Feature #16746] by nobu · Pull Request #2996 · ruby/ruby](https://github.com/ruby/ruby/pull/2996)）

```ruby
def hello(name) =
  puts("Hello, #{ name }")

hello("endless Ruby") #=> Hello, endless Ruby
```
```ruby
def inc(x) = x + 1

p inc(42) #=> 43
```
```ruby
x = Object.new

def x.foo = "FOO"

p x.foo #=> "FOO"
```
```ruby
def fib(x) =
  x < 2 ? x : fib(x-1) + fib(x-2)

p fib(10) #=> 55
```

ちなみに、チケットに記載されていますが、エンドレスメソッドの対象になる以下のようなコードは、ruby/rubyのコードベースで24%を占めていたらしいです。

```ruby
# three-line style
def value
  @val
end

# one-line style
def value; @val; end
```

```ruby
def hoge = 'hoge'
```

https://twitter.com/mametter/status/1367639310342615040?t=tXB8_5gXzkd6Gny66uAyeg&s=19

#### 注意点

https://techlife.cookpad.com/entry/2020/12/25/155741

セッターメソッドはエラーになります。

```ruby
def set=(x) = @x = x
#=> setter method cannot be defined in an endless method definition
```

defと同じスコープのローカル変数へはアクセスできないです。

```ruby
hoge = 'hoge'

def hoge_length = hoge.length
#=> undefined local variable or method `hoge' for main:Object (NameError)
```

## 感想

右代入もエンドレスメソッドも、使いどころはありそうですが、ルールなしにプロダクションのコードに混在すると混乱しそうだなと感じました。
Qiita開発メンバーの中でも、どういうタイミングで使うべきかを議論しましたが、各チームごとに使い所を話し合ったほうがいいのかもと感じました。

## 参考

https://bugs.ruby-lang.org/issues/15921

https://bugs.ruby-lang.org/issues/15799

https://techlife.cookpad.com/entry/2020/12/25/155741

https://techlife.cookpad.com/entry/2021/12/25/220002

https://techlife.cookpad.com/entry/2022/12/26/121950

https://qiita.com/jnchito/items/9bb4aa1dcefa00257815

https://speakerdeck.com/k_tsj/pattern-matching-new-feature-in-ruby-2-dot-7