Small Changes

[読書メモ]メタプログラミングRubyの4章ブロックについて復習

目的

  • メタプログラミングRubyの本を読みながらブロックやProcやlambdaなどが出てきたときに、普段使わないのでどういう流れで処理をするのか忘れてしまう。自分の理解しやすい形で整理したチートシートみたいなものを作って、すぐに思い出せるようにする。

ブロックの基本

  • ブロックの書き方(2種類)

    • 波かっこ

    • do...end

  • 慣習

    • 一行で書ける場合は「波かっこ」を使い、複数行のときは「do...end」を使う慣習がある

  • ブロックの実行

    • yieldを使う

    • yieldは任意の引数を渡すことができる。例だと、ブロックの|x, y|の部分に対応

  • ブロックをyieldで実行する例(「波かっこ」と「do...end」の書き方別)

    • def a_method(a, b)
        puts a + yield(a, b)
      end
      
      # 波かっこ
      a_method(1, 2) {|x, y| x * y}
      #=> 3
      
      # do...end
      a_method(1, 2) do |x, y|
        x * y
      end
      #=> 3

  • ブロックの有無の確認

    • block_given?を使う

    • def a_method
        puts block_given?
      end
      
      a_method                #=> false
      a_method {|x, y| x * y} #=> true

  • ブロックのスコープ

    • ブロックはブロックを定義した周辺の束縛を一緒に連れて行ってくれる

    • 下の例だと、ブロックと同じスコープの中に定義されているx = "Hello"をブロックが実行されるスコープ(my_methodの中)のところまで運んでくれる。

    • def my_method
        puts yield("world!!")
      end
      
      x = "Hello"
      my_method {|y| "#{x} #{y}"} #=> Hello world!!

    • ブロック実行時にx = "Good Bay"を渡そうとしても、ブロックからはx = "Good Bay"は見えないため、下記の場合の出力結果はHello world!!となる

    • def my_method
        x = "Good Bay" # ブロックからこのxは見えない
        puts yield("world!!")
      end
      
      x = "Hello"
      my_method {|y| "#{x} #{y}"} #=> Hello world!!

Procの基本

  • Procとは

    • Proc は ブロックをオブジェクトにしたもの

  • ブロックを Proc に変換する方法

    • Proc.newlambda を使う2種類がある。

    • p1 = Proc.new {|x| p x}
      p p1.class #=> Proc
      
      p2 = lambda {|x| p x}
      p p2.class #=> Proc

    • lambda は以下のような書き方もできる

    • p3 = ->(x) {p x} # p2と同じ意味
      p p3.class #=> Proc

  • &修飾

    • &修飾を使う場面

      • メソッドに渡した ブロック を Procオブジェクト に変換したいとき

      • メソッドに渡した Procオブジェクト を ブロック に変換したいとき

    • def math(a, b)
        yield(a, b)
      end
      
      def do_math(a, b, &operation)
        p operation.class #=> Proc
        math(a, b, &operation)
      end
      
      p do_math(2, 3) {|x, y| x * y} #=> 6

      • 1. do_math を実行したときに {|x, y| x * y} というブロックを渡している。

      • 2. do_math の引数列の最後に&operationとしているので、 ブロック{|x, y| x * y} が Procオブジェクトに変換される。

      • 3. math にProcオブジェクトを渡すときに &operationとしているので、Procオブジェクトが ブロック{|x, y| x * y} に変換されて渡される。

  • Proc.newで作るProc と lambdaで作るProc の違い

    • どちらも同じProcオブジェクトだが、Proc.newで作るProcオブジェクト と lambdaで作るProcオブジェクトは厳密には違いがある。(lambda?とすると、lambdaで作られたProcオブジェクトか確認できる)

      • p1 = Proc.new {|x| p x}
        p p1.lambda? #=> false
        
        p2 = lambda {|x| p x}
        p p2.lambda? #=> true

    • Proc.newlambda の比較

      • lambda のほうが直感的な挙動をするため、Rubyistの多くはlambda を使うとのこと

      • キーワードProc.newlambda
        returnProc.callされたスコープでreturnする ※意図せずスコープを抜けてしまう可能性があるブロックの中でreturnする
        引数Proc.call時に引数が過剰な場合は切り落としてくれる。引数が不足している場合はnilを割り当ててくれるProc.call時に引数の数が合わないとArgumentErrorになる

  • まとめ

  • Procとlambdaはなんとなく別物だという認識だったが、書き方が違うだけでどちらもProcオブジェクトを生成するという点は同じということを理解できたのは良かった。