1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rubyの&って真逆の2つの意味を持つよねって話

Last updated at Posted at 2020-10-08

ここでの&はビット演算子ではなくProc関連で出てくるアレのことです。
知ってる人にとっては当たり前のことかもしれませんが、自分的にわかりやすい(それでいて他にこういう書き方をしてる記事がなかった)ので書き留めておきます。

そもそも&と言えば

ブロックをProcオブジェクトに変換する記法です。最も単純な例で

def test(&fun)
    fun.call
end

test { p 1 } #=> 1

これは1と表示されます。
・ブロック { p 1 } がfunに渡される
・このfunは&によってProcオブジェクトに変換される。
・そのため、funはProcのメソッドの1つであるcallを用いた呼び出しを受け付けるようになる
という算段ですね。

ちなみに本題からは外れますが、Rubyでは
・「yieldとは&引数で生成されたProcを.callする略記法である」
・「yieldに渡す&引数Procはメソッド定義行で省略して構わない」
と定められていますので、上記は

def test
    yield
end

test { p 1 } #=> 1

でもOKです。しんぷる!

では、次は?

def test(&fun)
    fun.call
end

proc = Proc.new { p 1 }
test &proc #=> 1

これも1と表示されます。
ここで最初私は、
「(この部分は間違いです)&はProcへの変換のおまじないだから、もともとProcのものに&をつけても相変わらずProcのままで、&procでも&funでもProcオブジェクトが渡されてるのかな」
と思いました。

上記が正しければ、片方の&を取り払っても問題ないはずですね。ずっとProcが渡されているはずですから。
実際にやってみると…

def test(fun)
    fun.call
end

proc = Proc.new { p 1 }
test &proc
#=> wrong number of arguments(given 0, expected 1)

あれ?エラーですね。
何が起こったのでしょう。試しに&procのクラスを調べてみましょうか。

proc = Proc.new { p 1 }
p proc.class #=> Proc
# p (&proc).class #=> syntax Error

どうやらprocと&procは別物。&procは.classそのものを受け付けないようです。
それもそのはず。実は、&procは__オブジェクトですらない__からです。
Ruby公式ドキュメントには、

ブロック付きメソッドに対して Proc オブジェクトを `&' を指定して渡すと
呼び出しブロックのように動作します。

と、ちゃんと書かれておりました。そして、Rubyでブロックはオブジェクトではありません(逆に言えば、ブロックをオブジェクトとして扱いたいという欲求から出てきたのがProcなのだと思います)。

こうして、&には__二つの意味__があることがわかります。
・ブロックからProcオブジェクトへの変換
・Procオブジェクトからブロックへの変換
真逆ですね。

うーん…正直奇妙に思えましたが、慣れるとこれがむしろしっくりくるのでしょうか。

余談

上でエラーになったコードですが、そもそも&を使わずにProc状態でどんどん受け渡せば問題なく動きます。

def test(fun)
    fun.call
end

proc = Proc.new { p 1 }
test proc #=> 1

随分すっきりしました。
&を使わない引数なら、複数のProcも問題なく受け渡せます(&付き仮引数の場合はメソッドにつき1つだけと決められている)。

def test(one, two)
    one.call
    two.call
end

proc1 = Proc.new { p 1 }
proc2 = Proc.new { p 2 }

test proc1, proc2
#=> 1
#   2

こうしてみると、仕様上は、Procでないブロックを定義せずとも十分言語として機能するということになりそうです。

しかし、やっぱりRubyっぽくmapなどには直接ブロックを渡したいですし、先に述べたyield記法は捨てがたい。とすると可読性の上で&は大事ですので、コイツとは末永く付き合っていくことになりそうだな、と思ったのでした。
そこで付き合うコツとして、&の意味が文脈で変わってくるので、(これはすべての型やクラスに言えることかとは思いますが)プログラムの中で「これは今Procとブロックのどちらなのか」を常に把握するのが大事なのかな、というのが今の考えです。

参考

大変参考になりました。ありがとうございました。
Ruby block/proc/lambdaの使いどころ
Procを制する者がRubyを制す(嘘)

1
0
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?