ウェブエンジニア珍道中

日々の技術的に関する経験を書いていきます。脱線もしますが助けになれば幸いです。

RubyのProcについてまとめる

RubyのProcとかMethodとかの理解が少し進んできたのでまとめる

今回はProcについて

Procとは

Rubyのブロック文をオブジェクト化したもの

ブロック文はdo ~ end{ ~ }でよくつかう一連の処理をまとめたもの

だからといってdo ~ endを直接変数に入れるとかはできない。以下の方法で作成する

# 以下2つはエラー
hoge = do do_anything end
hoge = { do_anything }

# Proc.newを使う
print_proc = Proc.new { |msg| puts msg } 
# Kernel.procを使う
print_proc = proc { |msg| puts msg }
# Kernel.lambdaを使う
print_proc = lambda { |msg| puts msg }
# アロー演算子を使う
print_proc = ->(msg) { puts msg }

lambdaとProc.newでは引数でエラーが出るかどうかといった挙動が少し違ったりするがここでは割愛

使いみち

call[]で直接実行ができる

print_proc = lambda { |num| puts num }

print_proc.call(1)
=> 1
print_proc[1]
=> 1

あとはeachなどブロック文を受け取るメソッドにわたす

# 直接block文を渡す場合
[1, 2, 3, 4].each do |num|
  puts num
end

# procを作成して渡す場合
print_proc = lambda { |num| puts num }
[1, 2, 3, 4].each(&print_proc)

[1, 2, 3, 4].each(&print_proc)&はprocを渡すとブロックとして取り扱ってくれる役割がある。procをブロック文に展開してメソッドに渡すイメージ

# ↓に展開されて動作しているイメージ
[1, 2, 3, 4].each { |num| puts num }

each(&:hoge)は何をしてるのか

よく見る書き方(eachだと挙動がわかりにくいのでmapにした。nextは次の整数を返すメソッド)

[1, 2, 3].map { |num| num.next }
=> [2, 3, 4]

# 上記のような処理の場合、こう書くのが一般的
[1, 2, 3].map(&:next)
=> [2, 3, 4]

「各要素に対してメソッドを一つだけ実行する時のイディオム」位のノリで覚えてたが実際はもっと複雑だった

map(&:next) の&部分はprocを受け取るとブロックとして解釈させるのはさっきと同じ。今回違うのはここに:nextというsymbolを渡しているというところ。

map(&proc)

map(&:next)

実はこの&にはまだ役割があり、symbolを渡された時には暗黙的にto_procメソッドを実行し、procに変換した上で更にブロック文に展開を行う

流れは以下の通り
:next → (:next.to_procを実行) → proc → ブロック文

この手順が(&:next)に含まれている。めっちゃ隠れて色々やってる

to_procの役割

docs.ruby-lang.org

「受け取った引数に対して、そのシンボル名のメソッドを実行する」procを作成するイメージ(ちょっと語弊ありそう

nextメソッドの場合

next_proc = :next.to_proc
next_proc.call(1)
=> 2

受け取った数字に対して.nextを実行するprocを作成している、挙動は以下と同じ

next_proc = lambda { |num| num.next }
next_proc(1)
=> 2

総合するとmap(&:next)は「 :nextにこっそりto_procを叩くことで、【引数に対してnextを実行する】procを作成して、ブロック文と解釈してmapにわたす」ということをしている

引数がいる場合

+など引数が必要なメソッドは第2引数に入れる

add_proc = :+.to_proc

add_proc.call(1, 4)
=> 5
add_proc[1, 9]
=> 10