Ruby でメソッドの戻り値を受け取るかどうかを判定する RubyVM.return_value_is_used? が面白そう

こんな深夜に Ruby で面白そうなチケットを見かけたのでいろいろと試してみました。

注意

  • これはまだ開発中の機能になり今後挙動が変わる可能性があります
  • またこの機能はまだ議論されている途中で本体に組み込まれるか未定です

RubyVM.return_value_is_used? とは

RubyVM.return_value_is_used? は呼び出したメソッドの戻り値が受け取られるのか受け取られないのかを判定します。
例えば def hoge の中で RubyVM.return_value_is_used? を呼び出した場合、 hoge の戻り値が変数等に代入されれば true を返し、そうでなければ false を返します。
使い方は以下のような感じ

def hoge
  if RubyVM.return_value_is_used?
    pp "戻り値を受け取る"
  else
    pp "戻り値を受け取らない"
  end
end

# 戻り値を受け取らない
hoge          # "戻り値を受け取らない"

# 変数に代入する
value = hoge  # "戻り値を受け取る"

# 引数に渡す
Array hoge    # "戻り値を受け取る"

# 戻り値をチェーンする
hoge.nil?     # "戻り値を受け取る"

# 最後に呼び出したやつも?
hoge          # "戻り値を受け取る"

使い方自体はそこまで難しくなさそうですね。
ただし、以下のように書くと意図せず『戻り値を受け取る』ことになるので注意する必要はありそうです。

def hoge
  if RubyVM.return_value_is_used?
    pp "戻り値を受け取る"
  else
    pp "戻り値を受け取らない"
  end
end

def foo
  # hoge の戻り値をそのまま返しているので "戻り値を受け取る" ことになる
  hoge
end
foo    # "戻り値を受け取る"

def bar
  # hoge の戻り値は受け取っていないので "戻り値を受け取らない" ことになる
  hoge
  nil
end
bar    # "戻り値を受け取らない"

不要に戻り値を返さないように注意する必要がありそうですね。

ユースケース

いくつかユースケースを考えてみました。

class Hash
  def refresh(key)
    # 引数を受け取る場合のみ result を設定する
    if RubyVM.return_value_is_used?
      result = self[key]
    end
    self[key] = nil
    result
  end
end

homu = { name: "homu", age: 14 }
homu.refresh(:name)
p homu
# => {:name=>nil, :age=>14}

age = homu.refresh(:age)
pp homu  # => {:name=>nil, :age=>nil}
pp age   # => 14
class User < ActiveRecord::Base
  def update_name(name)
    update!(name: name)

    # 戻り値を受け取る場合のみ reload した値を返す
    reload if RubyVM.return_value_is_used?
  end
end

こんな感じで副作用を伴うメソッドだと割と多用しそうです。

所感

アプローチとしては面白いですね。
他の言語とかにも似たような機能ってあるんだろうか? 利用できそうなケースは割とありそうだと思いつつ、毎回メソッドを定義するたびに RubyVM.return_value_is_used? を使って処理を分岐するのは結構しんどそうな気がしました。
実際に使う場合はボトルネックになりそうなケースでのみ使用しそうですかね?使いこなすのがむずかしそう。
すでにチケットや PR のコメントが盛り上がっていますが、導入されると結構な目玉機能になりそうですねー。