「初めてのRuby」読書会の補足2(p36 mapの省略バージョン)

こんにちは.先輩を刺す刺さないとか言われてしまったきたけーです(刺さないです).前回から少し日が空きましたが前回同様に補足を書いていこうと思います.
今回はp36の例2-14です.

acids = ["Adenin", "Thymine", "Guanine", "Cytosine"]

String#downcaseは文字列に含まれる大文字のアルファベットを小文字にします.

"ABC".downcase # => "abc"

Array#mapは配列の各要素にブロックを適用し,適用した結果を新しい要素にした配列を返します.

acids.map {|a| a.downcase} # => ["adenin", "thymine", "guanine", "cytosine"]

# 省略バーション???
acids.map(&:downcase) # => ["adenin", "thymine", "guanine", "cytosine"]

さて,この省略バージョンはなにがどうなっているのでしょうか? 同期の間でざわめきがおきましたが,時間の関係上スルーの方向になりました.今日はこれを掘り下げてみます.

ブロックはオブジェクトなの?

前回,Rubyは文字列も数もクラスもなにかもがオブジェクトだと書きました.では,ブロックはどうなのでしょう? ブロックもオブジェクトなのでしょうか?
実は違います.ブロックはオブジェクトではありません.ただしProcオブジェクトというオブジェクトに変換することができます.ブロックをProcオブジェクトにするには「Proc.new ブロック」と記述します*1.

hoge = Proc.new {|a| a.downcase}

# ブロックをオブジェクトにしたProcオブジェクト
# 生成したProcオブジェクトはcallメソッドを呼ぶことでブロックのコードを実行できる
hoge.call("ABC") # "abc"

*1 「proc ブロック」, 「lambda ブロック」などの書き方もできますが,Proc.new・proc, lambdaで生成したProcオブジェクトには微妙に違いがあります.が,ここで書くとかなり話がズレるので触れません.

&は切り替えスイッチ

&.C言語に触れたことがある方にはポインタが思い浮かんでくるかもしれません.
Rubyの場合,&は「ブロックとProcオブジェクトを切り替えるスイッチ」のようなものです.

# hogeをやっぱりブロックとして使いたいなぁ
# &hogeでメソッド呼び出しにブロックとして渡す
acids.map(&hoge) # => ["adenin", "thymine", "guanine", "cytosine"] 

# これと同じ
#  acids.map do |a|
#    a.downcase
#  end

おや? この形どこかで見かけたような... 段々真実に近づいてきました.

Symbol#to_proc

ここで少し話がズレます.シンボルについて.
前回の説明で変数aとbの文字列の値が同じでもオブジェクトとしては別物だという話をしました.

a = "abc"
b = "abc"
a == b # true
a.equal?(b) # false

シンボルは文字列とは違い「同じ値であればオブジェクトとしても同じ」というものです.大抵は:からはじまり,識別子が続きます.

a = :abc
b = :abc
a == b # true
a.object_id # 894248
b.object_id # 894248
# オブジェクトとして同じ!
a.equal?(b) # true

シンボルにはto_procというメソッドがあります.厳密な定義は書きませんが,シンボルと同名のメソッドを使うProcオブジェクトを返します.

# これは
:downcase.to_proc

# これと(ほぼ)同じ
Proc.new {|a| a.downcase}

話をまとめよう!

あれこれ話してきましたがまとめましょう.

acids.map {|a| a.downcase} # => ["adenin", "thymine", "guanine", "cytosine"]

hoge = Proc.new {|a| a.downcase} # ブロックをいったんProcオブジェクトにしよう
acids.map(&hoge) # Procオブジェクトをブロックとして使いたい

# hogeは:downcase.to_procと(ほぼ)同じでした
# hogeを:downcase.to_procに置き換えます
acids.map(&:downcase.to_proc)

# &の後ろってふつうProcオブジェクトだよね.
# Procオブジェクトじゃないならto_procメソッドを呼んで,
# Procオブジェクトに勝手に変換しちゃってもいいよね.
# ということで
# 実は暗黙のうちにProcオブジェクトに変換してくれます
acids.map(&:downcase)

以上,省略バージョンを巡るちょっと長い旅でした.
前回同様,この説明おかしいだろって部分があったらコメントお願いします.

参考文献

初めてのRuby

初めてのRuby