ブロックdo 〜 end
や{|hoge| hoge.bar }
を受け取って何かするメソッドを自分で作る方法。
%w(hoge foo bar).each do |s| puts s end
のようなメソッドを自分で作りたくなる時は意外と多いので、覚えられるようにメモしておこうと思います。
自前のメソッドでブロックを実行する方法
主に、以下2通りがあるようです。
- ブロック引数
&block
を使う方法 yield
を使う方法
ブロック引数を使う方法
メソッドの引数の一番後ろに&
で始まる仮引数を定義すると、これでブロックを受け取ることができます。
&
がついているとブロックをProcオブジェクトに展開されるので、block.call
でブロックを実行することができます。
Proc
オブジェクトについては、まだよくわかってないので、今度調べたいと思います。
def execute_block(&block) block.call end execute_block do puts 'Hello' end # ↓出力 # Hello # => nil
ブロックに対して引数を渡すには、call
メソッドに引数を渡します。
def execute_block_with_args(&block) a = 'Hello' b = 'world' block.call(a, b) end execute_block_with_args do |hello, world| puts "#{hello}, Ruby #{world}!" end # ↓出力 # Hello, Ruby world! # => nil
call
に渡した値が、ブロックの引数||
に囲われた変数に渡されます。
yieldを使う方法
yieldを使うと、ブロック変数の宣言の省略ができる。
def execute_block yield('Hello', 'world') end execute_block do |a, b| puts "#{a}, Ruby #{b}!" end # ↓出力 # Hello, Ruby world! # => nil
ブロックは渡されたか?
yeild
を使う場合、&block
ブロック引数を省略できるため、メソッド自体がブロックが渡されたかどうかは、メソッドの中からはわかりません。
そのためかどうかは知りませんが、block_given?
というメソッドが用意されています。
block_given?
は、ブロックが渡された場合にtrue
になるため、メソッド内でブロックの有無によって分岐させることができます。
def give_hello_if_block_given if block_given? yield 'hello' else puts 'Please give me block!' end end give_hello_if_block_given do |a| puts "Block was given #{a}." end # ↓出力 # Block was given hello. # => nil give_hello_if_block_given # ↓出力 # Please give me block! # => nil
ブロックを渡すメリット
自作のメソッドでブロックを渡せると何が嬉しいかというと、あるオブジェクトを開いて使い終わったら必ずリソースを解放したい
みたいなときに便利だと思います。
以下に、とりあえず何かを開いた状態か開いていない状態かを管理するクラスSomething
を作りました。
Something
は、new直後に@opened
をfalse
で初期化します。何も開いていない状態です。
また、open
メソッドとclose
メソッドを持っており、@opened
の状態を書き換えることができ、opened?
で今の状態を問い合わせることができます。
class Something def initialize @opened = false end def open @opened = true puts 'Opened something' end def close @opened = false puts 'Closed something' end def opened? @opened end end
これを操作して、特定のスコープの範囲内でのみ、Something
を開き、範囲外になったらSomething
を閉じるような使い方をしたいと思います。
普通にやると以下のように、open
→ 何かやる
→ close
ですが、これはクローズのし忘れなどを招きやすくなります。
something = Something.new p something.opened? # ↓出力 # false # => false something.open # ↓出力 # Opened something # => nil p something.opened? # ↓出力 # true # => true something.close # ↓出力 # Closed something # => nil p something.opened? # ↓出力 # false # => false
以下のようにブロックを受け取るメソッドを作ると、そのブロックの範囲内でオブジェクトを開くことができるようになります。
def with_open_something(something) # メソッド開始時にオープンする処理をやる something.open if block_given? yield something end ensure # メソッドが終わる前に、とりあえずクローズする something.close end
上で
something = Something.new p something.opened? # ↓出力 # false # => false with_open_something(something) do |s| p something.opened? end # ↓出力 # Opened something # true # Closed something p something.opened? # ↓出力 # false # => false
with_open_something
を実行した際に、open
メソッドの実行に続けて、ブロック内の処理が実行され、その後close
するという動きになっていることがわかります。
まとめ
ブロックの使い方を実行しながら確認してみました。
Procオブジェクトが密接に絡んでくるようなので、Procを理解できるともっと色々できるかもしれません。今度調べてみたいと思います。
オープンしてクローズするパターンを試してみましたが、ある処理の前後の処理を共通化したい
みたいなときにも使えて便利だと思います。