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の役割
「受け取った引数に対して、そのシンボル名のメソッドを実行する」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