LoginSignup
7
2

More than 5 years have passed since last update.

Ruby で遅延 Proc 呼び出し

Posted at

TracePointネタです

こんな感じでコードを書くと

defer.rb
$proc_stack = Hash.new { |h, k| h[k] = [] }

TracePoint.trace(:return) do |tp|
  next if tp.method_id == :defer

  key = "#{tp.defined_class}::#{tp.method_id}"

  while block = $proc_stack[key].pop
    block.call
  end
end

module Kernel
  def defer(&block)
    b = caller_locations[0]
    key = "#{self.class}::#{b.base_label}"
    $proc_stack[key] << block
  end
end

これが

def hello
  defer { puts "world" }
  defer { puts "second" }

  puts "hello"
end

hello

こんな感じに

$ ruby defer.rb 
hello
second
world

これが

def count
  puts "counting"

  1.upto(10) do |i|
    defer { puts i }
  end

  puts "done"
end

count

こんな感じになります。

$ ruby defer.rb 
counting
done
10
9
8
7
6
5
4
3
2
1

こんな感じで defer で登録した Proc オブジェクトがそのメソッドの終わりに呼び出されるようになります。元ネタとしては golang の Defer を見て Ruby でも出来そうだなと思ったのがきっかけです。(正しく実装できているかは怪しいですが、ネタなのでその辺りはあまり気にしない方向で)

仕組み

実装としては、まず defer 呼び出し時に caller_locations を呼び出して、defer の呼び出し元を調べます。その後、どこから呼び出されたのか を key にして $proc_stack に渡された Proc オブジェクトを登録しておきます。

そしてメソッドの終了時に TracePoint の return イベントが発生するので どのメソッドが終了したのか を調べ、 $proc_stack に登録されている Proc オブジェクトがなくなるまでスタックから取り出して call しています。

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