Rubyのブロック構文について

アプリケーション

概要

Rubyのブロック構文についてかく。

ブロック構文とは

他メソッドにコード断片を渡すことで、そのメソッド内で処理を実行させることができる構文。

do..endまたは{}を使って定義する。ブロックは引数を受け取ることができる。

# do..end
[1...3].each do |number|
  puts number
end

# {}
[1...3].each { |number| puts number}

ブロックを受け取るメソッドを定義することもできる。

def greet
  puts "Hi"
  # 渡されたブロックを実行
  yield if block_given? # block_given?はブロックが渡されたかどうかを判定するメソッド
  puts "Bye"
end

# ブロックを渡す
greet do
  puts "Hello"
end

# 出力:
# Hi
# Hello
# Bye

また、ブロックをメソッドの引数として渡すこともできる。その場合は&を使って渡す。

def greet(&block)
  puts "Hi"
  block.call if block
  puts "Bye"
end

greet do
  puts "Hello"
end

# 出力:
# Hi
# Hello
# Bye

to_procメソッドを持つオブジェクトもブロックとして渡すことができる。

class Greeting
  def to_proc
    Proc.new { |name| puts "Hello, #{name}!" }
  end
end

%w(Alice Bob Charlie).each(&Greeting.new)

# 出力:
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!

ブロックはProcやlamdaと同じくクロージャーである。

def count(&block)
  puts block.call
  return block
end

x = 0
b = count do
  x + 1
end # =>1

x += 10
puts b.call # =>11

do..end{}は結合度が異なり、{}の方が結合度が高い。

array = [1, 2, 3]

# 結合度が低いのでブロックがmapに渡される前に評価される(ppに渡されてしまう)
pp array.map do |x|
  x * 2
end

# 出力:
#<Enumerator: ...>

# 結合度が高いのでブロックがmapに渡されて評価される
pp array.map { |x| x * 2 } # => [2, 4, 6]

ブロックの中で単一のメソッドを呼び出す場合に限り、ブロックをシンボルで置き換えることができる。シンボルにはto_procメソッド(Symbol#to_proc)が実装されている。

# 通常の形
numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.select { |n| n.even? }
p even_numbers.inspect # => [2, 4]

# シンボルを使った形
numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.select(&:even?)
p even_numbers.inspect # => [2, 4]

# 自作のメソッドでシンボルを使った形
module Numbers
  def my_even?
    self % 2 == 0
  end
end

class Integer
  include Numbers
end

puts [1, 2, 3].select(&:my_even?)  # => [2]

do..end{}の使い分けとしては、次のような考え方がある。

  • 複数行の場合はdo..end
  • 一行の場合は{}
  • 値を返す式として使う場合は{}
  • 手続きを渡す場合はdo..end

コーディング規約に依る部分もあるので、あくまで一例として考えておきたい。

参考


関連書籍