1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

知ってるようで知らない!Rubyブロックの奥深き世界!

1
Last updated at Posted at 2026-02-22

Rubyのブロック、Proc、lambdaを理解する

概要

この記事は、「メタプログラミングRuby 第2版」第4章を読み学習した内容を個人学習用にまとめ直したものです。

Rubyのブロックは、強力かつ柔軟な機能の一つです。本記事では、ブロックの基本的な仕組みから、Proc、lambdaとの違い、実践的な使い方まで解説します。

ブロックはクロージャ

ブロックを{}もしくはdo...endで定義すると、定義された同じレベルに存在する変数を参照することができる。

yieldでメソッドに渡した場合などでは、この変数への参照も引き継ぐことができる(クロージャ)。

def my_method
  x = "Goodbye"
  yield("cruel")
end

x = "Hello"

# ブロックを作成すると、同じレベルのxへの参照を引き継ぐ
my_method {|y| "#{x}, #{y} world"} # => "Hello, cruel world"

Ruby 3.4以降では、ブロック引数が1つの場合にitで暗黙的に参照できる。

[1, 2, 3].map {|x| x + 1}  # 従来の書き方
[1, 2, 3].map { it + 1 }   # itで簡潔に書ける(Ruby 3.4+)

一方、ブロック内で作成した変数はブロックの外では参照できない。

def just_yield
  yield
end

top_level_variable = 1

just_yield do
  top_level_variable += 1
  local_to_block = 1 # 新しい変数を定義
end

top_level_variable # => 2
local_to_block # => Error

スコープゲートとは?

プログラムが以下の境界に出入りすると、スコープが変化する(スコープゲート)。

  • クラス定義(class)
  • モジュール定義(module)
  • メソッド(def)
# 外側のスコープ
variable = "外側の変数"

class MyClass
  # クラス定義のスコープゲート
  # puts variable  # => NameError: 外側の変数にアクセスできない

  class_variable = "クラス内の変数"

  def my_method
    # メソッド定義のスコープゲート
    # puts variable        # => NameError
    # puts class_variable  # => NameError

    method_variable = "メソッド内の変数"
    puts method_variable  # => OK
  end

  module MyModule
    # モジュール定義のスコープゲート
    # puts variable        # => NameError
    # puts class_variable  # => NameError
  end
end

スコープを共有する方法

フラットスコープ

スコープゲートのキーワードを代用できるメソッド(Class.new, define_method)呼び出しに変更すれば、ゲートの外のスコープを共有できるようになる(フラットスコープ)。

variable = "外側の変数"

MyClass = Class.new do # classの代用
  # puts variable  # => OK

  class_variable = "クラス内の変数"

  define_method :my_method do # defの代用
    # puts variable        # => OK
    # puts class_variable  # => OK
  end
end

共有スコープ

複数のメソッドで変数を共有したいが、その他からは見えないようにしたい場合はスコープゲートでスコープを閉じた後に、フラットスコープで変数を共有すればいい(共有スコープ)。

def define_methods # スコープゲートで外部から遮断
  shared = 0

  # Kernelメソッドをフラットスコープ化
  Kernel.send(:define_method, :increment) do
    shared += 1
  end

  Kernel.send(:define_method, :counter) do
    shared
  end
end

define_methods

increment  # => 1
increment  # => 2
counter    # => 2

# shared変数には外部から直接アクセスできない(カプセル化されている)
# puts shared  # => NameError

instance_eval

BasicObject#instance_evalを使用することで、オブジェクトのコンテキストでブロックを評価することもできる。

instance_evalに渡したブロックは、レシーバをselfにしてから評価され、レシーバのprivateメソッドやインスタンス変数にアクセスできる。

また、他のブロックと同様、instance_evalを定義したレベルの変数も参照できる。

class MyClass
  def initialize
    @value = "インスタンス変数"
  end

  private

  def secret_method
    "プライベートメソッド"
  end
end

obj = MyClass.new
variable = 1

# instance_evalでオブジェクトのコンテキストに入る
obj.instance_eval do
  puts self.class       # => MyClass(selfがobjになる)
  puts @value           # => "インスタンス変数"(インスタンス変数にアクセス可能)
  puts secret_method    # => "プライベートメソッド"(プライベートメソッドも呼べる)
  puts variable         # => 1(通常のブロックと同様に定義箇所と同じレベルの変数を参照できる)
end

# 外側からは通常アクセスできない
# obj.secret_method  # => NoMethodError

instance_exec

以下の例のように、インスタンス変数はself(レシーバ)のコンテキストで評価されるので、instance_evalブロックの外側で定義されたインスタンス変数にはアクセスできない。

class C
  def initialize
    @x = 1
  end
end

class D
  def twisted_method
    @y = 2
    C.new.instance_eval { "@x: #{@x}, @y: #{@y}"} # ここでレシーバが変更される
  end
end

# インスタンス変数はself(レシーバ)によって決まるので外側の@yにはアクセスできない
D.new.twisted_method # => "@x: 1, @y: "

オブジェクトのコンテキストで処理を実行しつつ、外部から値を渡したい場合はinstance_execを用いる。

class D
  def twisted_method
    @y = 2
    C.new.instance_exec(@y) {|y| "@x: #{@x}, @y: #{y}"}
  end
end

D.new.twisted_method # => "@x: 1, @y: 2"

クリーンルーム

ブロックを評価するためだけに作られたオブジェクトをクリーンルームと呼ぶ。

既存のオブジェクトでinstance_evalを使うと、そのオブジェクトのインスタンス変数やメソッドと衝突するリスクがある。

クリーンルームを使えば、余計な状態を持たない安全な環境でブロックを評価できる。

# 既存のオブジェクトで評価すると衝突のリスクがある
class MyClass
  def initialize
    @name = "元の値"
  end
end

obj = MyClass.new
obj.instance_eval { @name = "上書き" }  # 既存の@nameを壊してしまう

# クリーンルーム: 空のオブジェクトで安全に評価する
clean = BasicObject.new
clean.instance_eval do
  @name = "新しい値"  # 衝突の心配がない
end

BasicObjectはRubyのクラス階層の最上位にあり、メソッドが最小限なので、よりクリーンルームとして適している。

ブロックを呼び出し可能オブジェクトにする

呼び出し可能オブジェクト

評価ができてスコープが持ち運べるコードのこと。

Procオブジェクト

ブロックをオブジェクトに変換するために、Procクラスが用意されている。

inc = Proc.new {|x| x + 1} # ブロックを渡してオブジェクトを作成
inc.call(2)                # ブロックを後で評価できる

# その他のProcオブジェクトを作成する方法
# inc = lambda {|x| x + 1}
# inc = ->(x) { x + 1 }  

& 修飾でブロックとオブジェクトを相互に変換する

ブロックとProcオブジェクトを相互に変換するには&修飾を使う。

ブロック → Proc(メソッドの引数で受け取る)

メソッド定義の最後の引数に&を付けると、渡されたブロックがProcオブジェクトに変換される。

def my_method(&block)
  block.class  # => Proc(ブロックがProcに変換されている)
  block.call   # Procオブジェクトとして呼び出せる
end

my_method { "ブロックです" }  # => "ブロックです"

Proc → ブロック(メソッド呼び出しで渡す)

逆に、Procオブジェクトに&を付けてメソッドに渡すと、ブロックに変換される。

my_proc = Proc.new { |x| x * 2 }

# &を付けてProcをブロックとして渡す
[1, 2, 3].map(&my_proc)  # => [2, 4, 6]

# 上記は以下と同じ意味
[1, 2, 3].map { |x| x * 2 }  # => [2, 4, 6]

メソッドを呼び出し可能オブジェクトにする

Object#methodを使うと、メソッドをMethodオブジェクトとして取得できる。MethodオブジェクトはProcと同様にcallで呼び出せる。

class MyClass
  def my_method(x)
    x * 3
  end
end

obj = MyClass.new

# メソッドをMethodオブジェクトとして取得
m = obj.method(:my_method)
m.class     # => Method
m.call(2)   # => 6

MethodとProcの違い

Methodオブジェクトは定義されたオブジェクトのスコープで評価される。一方、Proc(lambda)は定義されたスコープ(クロージャ)で評価される。

class MyClass
  def initialize
    @x = 10
  end

  def my_method
    @x
  end
end

# Methodオブジェクト: 定義されたオブジェクトのスコープで評価される
m = MyClass.new.method(:my_method)
m.call  # => 10

# lambda: 定義されたスコープ(クロージャ)で評価される
x = 20
l = -> { x }
l.call  # => 20

UnboundMethod

Module#instance_methodを使うと、特定のオブジェクトに束縛されていないUnboundMethodを取得できる。UnboundMethodはそのままでは呼び出せず、bindでオブジェクトに束縛してから使う。

# UnboundMethodを取得
unbound = MyClass.instance_method(:my_method)
unbound.class  # => UnboundMethod
# unbound.call  # => エラー: そのままでは呼び出せない

# オブジェクトに束縛して呼び出す
bound = unbound.bind(MyClass.new)
bound.call  # => 10

# 別のクラスに束縛して呼び出すこともできる
another_bound = unbound.bind(AnotherClass.new)
bound.call # => 20

呼び出し可能オブジェクトのまとめ

全て、Proc.newMethod#to_proc&修飾などを使ってある呼び出し可能オブジェクトから別の呼び出し可能オブジェクトに変換できる。

種類 クラス スコープ return 呼び出し時の引数
ブロック オブジェクトではないが呼び出し可能 定義されたスコープで評価 呼び出し可能オブジェクトの元のコンテキストから戻る 引数は柔軟(足りない場合はnilで補完)
Proc(非lambda) Proc 定義されたスコープで評価 呼び出し可能オブジェクトの元のコンテキストから戻る 引数は柔軟(足りない場合はnilで補完)
Proc(lambda) Proc(lambda?がtrue) 定義されたスコープで評価 呼び出し可能オブジェクトから戻る 引数は厳密(足りない場合はArgumentError)
Methodオブジェクト Method 定義されたオブジェクトのスコープで評価 呼び出し可能オブジェクトから戻る 引数は厳密(足りない場合はArgumentError)
returnの違い
def proc_return
  p = Proc.new { return "proc から return" }
  p.call
  "ここには到達しない"
end

def lambda_return
  l = lambda { return "lambda から return" }
  l.call
  "ここに到達する"
end

# Proc の return はメソッド自体を抜ける
puts proc_return    # => "proc から return"

# lambda の return は lambdaの中だけで完結する
puts lambda_return  # => "ここに到達する"

まとめ

  • ブロックは定義時のスコープを持ち運ぶクロージャである
  • class/module/defスコープゲートとしてスコープを区切る
  • Class.newdefine_methodでスコープゲートを回避し、変数を共有できる(フラットスコープ
  • instance_evalでオブジェクトのコンテキストに入り、privateメソッドやインスタンス変数にアクセスできる
  • ブロックはProc.newlambda呼び出し可能オブジェクトに変換でき、&修飾でブロックとProcを相互変換できる
  • Methodオブジェクトは定義されたオブジェクトのスコープで、Procは定義されたスコープ(クロージャ)で評価される

参考文献

この記事は以下の情報を参考にして執筆しました。

1
1
2

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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?