改めて整理する Ruby 3.0 に実験的に入る予定のエンドレスメソッド定義構文と右代入演算子について

以前に以下の記事で右代入演算子とエンドレスメソッド定義構文について簡単に解説しました。

上記の記事から少し挙動に変更があったので Ruby 3.0.0 preview1 が出たタイミングで改めて挙動を整理したいと思います。

注意

ここに書かれているのはあくまでも Ruby 3.0.0 preview1 時点での挙動になります。
今後、挙動が変わったりそもそも機能ごと削除される可能性もあるので注意してください。

右代入演算子

今までは = を使って左辺の変数に右辺の値を代入することで変数定義を行っていました。

# 左辺に右辺の値を代入
result = 42

Ruby 3.0 から追加される => 演算子を使うと『左辺の値を右辺の変数に』代入する事ができます。 

# 左辺を右辺に代入
42 => result

また、多重代入も可能です。

[1, 2] => a, b

ただし、以下のような代入はできないので注意してください。

# error
# see: https://bugs.ruby-lang.org/issues/17190
1, 2 => a, b

右代入演算子があると左から右にコードを書いた流れでそのまま変数定義ができるので気持ちよくコードを書くことができますね。
特に irb などでコードを書いているときに変数を定義したいと思ったときにいちいち左側にカーソルを移動するのは結構ストレスだったので右代入で変数定義できるとかなり気持ちよさそうです。便利。
bugs.ruby のチケットはこちらです。

右代入の注意点

便利そうにみえる右代入なんですがちょっと注意点があり、例えば以下のようなコードを書きたいと思うことがあります。

# func a の結果を result に代入したい
func a => result

しかし、これは意図した動作をしません。
なぜなら上記の Ruby のコードは Ruby 2.7 現在でも有効であり、以下のように解釈されるためです。

# func に { a => result } のハッシュを渡すことになる
func({ a => result })

なので、意図する代入を行いたい場合は必ず左辺に括弧を付けて右代入を行う必要があります。

# これなら期待する動作になる
fucn(a) => result

これはおそらくうっかり書いてしまう事があるので注意する必要があります。

エンドレスメソッド定義構文

Ruby 3.0 ではエンドレスメソッド定義構文という機能が実装されます。
これは endless、つまり def だけでメソッドが定義できる構文になります。

# 今までは def ~ end でメソッドを定義していた
def square(x)
  x * x
end

# これが def だけで定義できるようになる
def square(x) = x * x

また、以下のように () がなかったり = がついてるメソッドはエラーになります。

# OK
def set_value(value) = @value = value
def value() = @value
def invalid?() = @value.nil?


# NG
# () を付ける必要がある
# syntax error, unexpected '=', expecting ';' or '\n'
def value = 42

# 名前に = がついてるメソッドは定義できない
# error: setter method cannot be defined in an endless method definition
def value=(value) = @value = value

エンドレスメソッド定義構文と右代入演算子を組み合わせると…

次のようにエンドレスメソッド定義構文と右代入演算子を組み合わせて使う場合に注意が必要です。

def set_value(value) = value => @value

(def set_value(value) = value) => @value

と解釈されます。
更に

private def set_value(value) = value => @value

private({ (def set_value(value) = value) => @value })

と解釈されます。
なので実際に実行すると次のようなエラーになります。

# error: `private': {:set_value=>nil} is not a symbol nor a string (TypeError)
private def set_value(value) = value => @value

こんなコードを書かないと思う人がいると思うんですがやってることは割と素直な書き方だと思うのでついうっかり書いてしまいそうです…。