JQ Blog

Rubyのblock, Proc, Lambda

ブロック

ブロックとは

  • do~endまたは{~}で囲われた引数となるためのもの
  • それ単体では存在できず、メソッドの引数にしかなれない
  • 引数として渡されたブロックは、yieldによって実行される
1
2
3
4
5
6
7
8
def my_method
  yield
end

my_method do
  p "Hello, block!"
end
=> "Hello, block!"

説明

  • 引数を明示
1
2
3
4
5
6
7
8
def my_method(&block)
  block.call
end

my_method do
  p "Hello, block!"
end
=> "Hello, block!"
  • 「&」とは
    &を付けることで、実際に引数にブロックが渡ってきた際、
    Procオブジェクトに変換している。

  • Procオブジェクトとは

    • ブロックをオブジェクト化したものがProc
      • ブロックをオブジェクトに変換することで、引き渡されたメソッド内で扱えるようにする
    • Procオブジェクトは、callで呼び出すことが出来る
  • 注意点
    引数として渡せるブロックは一つだけ。

  • yieldが使える
    引数として渡されるブロックが1つに限られているならば、呼び出し箇所をblock.callと明示せずに、yieldで統一する。

1
2
3
4
5
6
7
8
9
#メソッド定義
def my_method(&block)
  yield
end

my_method do
  p "Hello, block!"
end
=> "Hello, block!"

さらに、
yieldを使うなら引数の(&block)はいらないので

1
2
3
4
5
6
7
8
def my_method
  yield
end

my_method do
  p "Hello, block!"
end
=> "Hello, block!"

こういうふうに省略できる。

Proc

  • ブロックをオブジェクト化したものがProc
  • Procオブジェクトはcallで呼び出すことが出来る
  • Procに引数を持つこともできる
  • Proc.newとlambdaはほぼ同義

Procオブジェクトの定義と呼び出し

1
2
3
4
5
6
proc = Proc.new do
  p "hoge"
end

proc.call
=> "hoge"

Procに引数が渡された場合

1
2
3
4
5
6
proc = Proc.new do |s|
  p "Hello, #{s}!"
end

proc.call("Proc")
=> "Hello, Proc!"

Proc.newとlambdaはほぼ同義

1
2
3
4
5
6
7
lambda1 = lambda { p "hoge" }
lambda1.call
=> "hoge"

lambda2 = lambda { |s| p "Hello, #{s}!" }
lambda2.call("Proc")
=> "Hello, Proc!"

Procとlambdaの違い

  • lambdaはブロックの引数の数が違うとエラーを起こしてくれる。
1
2
3
4
5
6
7
8
9
proc = Proc.new { |a,b,c| p "#{a},#{b},#{c}" }
proc.call(2, 4)

=> 2,4,nil

lambda1 = lambda { |a,b,c| p "#{a},#{b},#{c}" }
lambda1.call(2, 4)

=> wrong number of arguments (2 for 3) (ArgumentError)
  • 明示的にreturnやbreakを行った場合の挙動が違います。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def method_proc
  proc = Proc.new { return p "proc"}
  proc.call
  p "method_proc"
end

method_proc
=>"proc"

def method_lambda
  lambda = lambda{ return p "lambda"}
  lambda.call
  p "method_lambda"
end

method_lambda
=>"lambda"
  "method_lambda"

ブロックやProcのメリット

柔軟に拡張できる

下のコードのようにメソッドを汎用的に使うことができる

1
2
3
def my_method(input, someProc)
  someProc.call(5, input)
end
1
2
3
4
5
6
sum_proc = Proc.new do |x, y|
  p x + y
end

my_method(3, sum_proc)
=> 8
1
2
3
4
5
6
string_proc = Proc.new do |x, string|
  p string * x
end

my_method('hello_proc! ', string_proc)
=> "hello_proc! hello_proc! hello_proc! hello_proc! hello_proc! "

クロージャとしての機能が得られる

サンプル①

1
2
3
4
5
6
7
8
9
def bar
  str = 'bye'
  yield('Jo')
end

str = 'hello'
bar { |name| "#{str}, #{name}" }

=> "hello, Jo"

サンプル②

1
2
3
4
5
6
7
8
9
10
11
12
13
14
n = 1

def foo(n)
  n = n + 1
end

foo(n)
=> 2
foo(n)
=> 2
foo(n)
=> 2
foo(n)
=> 2
1
2
3
4
5
6
7
8
9
10
11
n = 1
proc = Proc.new do
  n = n + 1
end

proc.call
=> 2
proc.call
=> 3
proc.call
=> 4

block_given?

block_given?とは

引数としてブロックが与えられたかどうかを判別するメソッド。

使い方

1
2
3
4
5
6
7
def greet_method
  if block_given?
    yield('hello!')
  else
    p "ブロックがありません。"
  end
end
1
2
3
4
greet_method do |str|
  p str
end
=> "hello!"
1
2
greet_method
=> "ブロックがありません。"

参考

https://qiita.com/kidach1/items/15cfee9ec66804c3afd2
https://qiita.com/ryo-ma/items/24c46592b45775e8644d