ねこものがたり

いちにちいっぽ

Rubyの&:method記法で引数ありのメソッドは呼び出せない

環境

$ rbenv version
2.7.0

お試しコード

class User
  attr_accessor :name

  def initialize(name)
    @name = name
  end

# 引数のいらないメソッド
   def print_name
     p @name
  end

# 引数のいるメソッド
  def greet(greet)
     p greet
  end
end


users = [User.new('neko314'), User.new('keiko')] # => [#<User:0x00007fe9b10c9778 @name="neko314">, #<User:0x00007fe9b10c9728 @name="keiko">]

引数不要のメソッドの場合

> users.each(&:print_name)
"neko314"
"keiko"

引数が必要なメソッドの場合

irb(main):020:1> users.each(&:greet("hello"))
irb(main):021:1>   
irb(main):022:1>     
irb(main):023:1>       
irb(main):024:1>         
irb(main):025:1>           
irb(main):026:1>             
irb(main):027:1>               
irb(main):028:1>                 
irb(main):029:1>     
(終わらない)              

2.6.5でやってみるとsyntaxエラーになりました

> users.each(&:greet("hello"))
Traceback (most recent call last):
        3: from /Users/keiko/.rbenv/versions/2.6.5/bin/irb:23:in `<main>'
        2: from /Users/keiko/.rbenv/versions/2.6.5/bin/irb:23:in `load'
        1: from /Users/keiko/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
SyntaxError ((irb):21: syntax error, unexpected '(', expecting ')')
users.each(&:greet("hello"))
                  ^
(irb):21: syntax error, unexpected ')', expecting end-of-input
users.each(&:greet("hello"))
                          ^

そもそも &:methodってなんなのか

2015年なのでちょっと古いけどUnderstanding Ruby's idiom: array.map(&:method)がよかったです。たぶん今でもこれなんじゃないかな。もし新しいのと差分があっても後で差分を追えばいいくらいの感じな気がします。

この記事中ににありました。

[].map(&MyClass.new)

# => trying to convert to a proc

私「あ!&:method& ってブロック引数の&:methodはシンボルなのか!!」 と気付きました。

なんとこれまで私はこの記法に対して思考を停止していたので&:でワンセットのように思っていました。

また

What Symbol#to_proc does is quite clever. It tries to calls a method with the same name (in our example, method_name) on the given object.

:upcase.to_proc.call("string")
# => STRING

という説明とサンプルがありました!これがかなりしっくり来ました。

なので、上述の私のサンプルコードで試してみても同じように実行できます。

> :print_name.to_proc.call(User.new('neko'))
"neko"

すごい!!

これを踏まえると、 [Ruby] ブロック変数とブロック引数とProc引数の違い|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社 の中で紹介されているように、&はじまりの引数はプロック引数と呼ぶほうがなんだか正体がわかように思います。

シンタックスエラーの正体

シンボルの文法を再確認してみました。

class Symbol (Ruby 2.7.0 リファレンスマニュアル)

:' の後には識別子、メソッド名(!',?',=' などの接尾辞を含む)、変数名 (`$'などの接頭辞を含む)、再定義できる演算子のいずれかに適合するものしか書くことはできません(そうでなければ文法エラーになります)

これを踏まえて、先程シンタックスエラーになったコードを見返してみます。

 users.each(&:greet("hello"))
Traceback (most recent call last):
        3: from /Users/keiko/.rbenv/versions/2.6.5/bin/irb:23:in `<main>'
        2: from /Users/keiko/.rbenv/versions/2.6.5/bin/irb:23:in `load'
        1: from /Users/keiko/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
SyntaxError ((irb):21: syntax error, unexpected '(', expecting ')')
users.each(&:greet("hello"))
                  ^
(irb):21: syntax error, unexpected ')', expecting end-of-input
users.each(&:greet("hello"))
                          ^

このシンタックスエラーはunexpected ')', expecting end-of-inputとはありますがシンボルの部分のシンタックスエラーのような気がしてます。でも不確か。

まとめ

完璧とはいかなくても&:methodがちょっと理解できたと思います。&: 不思議な独自記法と思ってたときは:&と書いたりしてましたが、意味がわかったのでそれもなくせそうです。