2
2

More than 3 years have passed since last update.

rubyのblockについて

Posted at

概要

なんとなくdo endを使う ということは分かっている人は多いと思いますが、
procとかが出てくると少しハードルが上がるため理解していない人が多かったように思います。
そのため改めて自分の知識の整理とともに、
多くの人がrubyの文法をしっかりと理解し効率よくコーディングができるようにまとめました。

blockとは

  • rubyではメソッドに対して一連の手続きを渡すことができる
  • blockは do end もしくは { } で記載する
  • 引数は |x|のように|ではさんだところに通常のメソッドとかと同じように記載する
[*1..3].each do |i|
  p i
end
# => 1
# => 2
# => 3

3.times { |i| p i }
# => 0
# => 1
# => 2

blockを使うメソッドのつくり方

  • blockを実行するには yieldを使用する or &blockを使用する(後述)
  • blockに仮引数を使用している場合はyieldに引数を渡すことで使用できる
  • yieldは何回も使用できる
  • yieldはblockが渡されていない場合に使用するとエラーになる
def three_times
  3.times { yield }
end
three_times { p 'a' }
# => "a"
# => "a"
# => "a"

def three_times
  3.times { |j| yield(j) }
end
three_times { |i| p i }
# => 0
# => 1
# => 2

def calculate
  p yield(1, 2)
  p yield(2, 3)
end

calculate { |a, b| a + b }
# => 3
# => 5
calculate { |a, b| a * b }
# => 2
# => 6

def three_times(&block)
  3.times { yield } rescue p $!
end
three_times
# => #<LocalJumpError: no block given (yield)>

blockが渡されたかどうかを判断する

  • block_given?を使用する
def three_times
  p block_given?
end

three_times
# => false
three_times { p 'y' }
# => true

blockをインスタンスとして扱いたい

  • blockは&を使用することでProcオブジェクトと相互変換できる
    • メソッドの引数として&proc_objectをすることで、ブロックとして渡せる
    • メソッドの仮引数として&blockとすることで、 ブロックをProcオブジェクトとして受け取れる
    • 引数で&を使わず素のblockを受け取る場合は、lambdaではなくprocになる(procとlambdaの違いは後述)
    • 引数でlambdaを&で渡した場合はlambdaとして受け取る
proc_object = Proc.new do |a|
  p a
end
3.times(&proc_object)
# => 0
# => 1
# => 2

def three_times(&block)
  3.times { |j| block.call(j) }
end
three_times { |i| i }
# => 0
# => 1
# => 2

procとlambda

  • ProcオブジェクトにはProcProc(lambda)というものが存在する(procとlambdaとそれぞれ呼ぶこととする)
    • どちらもProcクラス
  • procはProc.new { |x| } もしくは proc { |x| }と書く
  • lambdaはlambda { |x| } もしくは ->(x) { }と書く
p Proc.new {}
# => #<Proc:0x00007f3e59459978@/tmp/nvimaASrm8/2:1>
p Proc.new {}.instance_of?(Proc)
# => true
p lambda {}
# => #<Proc:0x00007f3e59459838@/tmp/nvimaASrm8/2:2 (lambda)>
p lambda {}.instance_of?(Proc)
# => true

procとlambdaの違い

引数の扱い

  • 引数が余分な場合
    • proc: スルーされる
    • lambda: エラーになる
  • 引数が足りない場合
    • proc: nilとなる
    • lambda: エラーになる
x = proc { |a, b| p [a,b] }
x.call(:a, :b, :c)
# => [:a, :b]

x = lambda { |a, b| p [a,b] }
x.call(:a, :b, :c) rescue p $!
# => #<ArgumentError: wrong number of arguments (given 3, expected 2)>

x = proc { |a, b| p [a,b] }
x.call(:a)
# => [:a, nil]

x = lambda { |a, b| p [a,b] }
x.call(:a) rescue p $!
# => #<ArgumentError: wrong number of arguments (given 1, expected 2)>

break, returnの挙動

  • Procを定義したメソッド内で実行した場合
    • return
      • proc: Procを囲むメソッドの結果となる
      • lambda: Procの結果となる
    • break
      • proc: LocalJumpErrorになる
      • lambda: Procの結果となる
  • Procを定義したメソッドの外で実行した場合
    • return
      • proc: LocalJumpErrorになる
      • lambda: Procの結果となる
    • break
      • proc: LocalJumpErrorになる
      • lambda: Procの結果となる

手続きの結果を返したいならlambdaでもprocでもnextを使えばよし!

def hoge
  x = proc { return 2 }.call
  p "x: #{x}"
  return 1
end
p "method: #{hoge}"
# => "method: 2"

def hoge
  x = lambda { return 2 }.call
  p "x: #{x}"
  return 1
end
p "method: #{hoge}"
# => "x: 2"
# => "method: 1"

def hoge
  x = proc { break 2 }.call
  p "x: #{x}"
  return 1
end
p "method: #{hoge}" rescue p $!
# => #<LocalJumpError: break from proc-closure>

def hoge
  x = lambda { break 2 }.call
  p "x: #{x}"
  return 1
end
p "method: #{hoge}"
# => "x: 2"
# => "method: 1"

def hoge(x)
  p "x: #{x.call}"
  return 1
end
x = proc { break 2 }
p "method: #{hoge(x)}" rescue p $!
# => #<LocalJumpError: break from proc-closure>

x = lambda { break 2 }
p "method: #{hoge(x)}"
# => "x: 2"
# => "method: 1"
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2