Guard オブジェクト

Perl だと Guard オブジェクトとかいうハックがあって、スコープを出るタイミングで必ず呼ばれるファイナライザを使って、あるスコープでだけ有効な処理を書けたりします。

例えば、DB のトランザクションや、あるいは以下のように依存するプロセスをあるスコープでだけ起動して終了するような用途で使われています。

{
    my $guard = Proc::Guard->new(command => [ "memcached", "-p", "12321" ]);
    # do something ...
};

# memcached has been killed

適当なメソッドにブロック(サブルーチン)を渡せばええやん、という気もしますし、実際 Ruby の transaction の場合そういう感じになります (Perl でももちろん同じようなサブルーチンを書くことはできます)。

ActiveRecord::Base.transaction do
    ....
end

しかし Perl の Guard オブジェクトと同じようなことをしたい! と思うと、Ruby の場合 finalizer の呼ばれるタイミングが GC が動いたタイミングなので、finalizer は使えません。

なので、多少妥協する必要があります。

複数のブロックのネストをフラットにする

上記のように依存プロセスを起動して勝手に終了してほしい、みたいな場合、ドンドコ他のプロセスも増やしていきたくなります。Ruby で書くなら以下のような感じでしょうか (実際このケースだと process メソッドなんて作らず単に begin / ensure / end を纏めて書いちゃうと思いますけど)

def process(cms, &block)
    fork { exec *cmd }
    yield
ensure
   Process.kill(:INT, pid)
end

process( [ "memcached", "-p", "12321" ]) do
    process(["foo"]) do
        process(["bar"]) do
             ....
        end
    end
end

ドンドコ作るとドンドコネストしまくっていきます。しかし、1個増えるたびにいちいち中身全部インデントしなおしていったらダルくて嫌な感じです。

Enumerator を使う

ブロックを持つ process をフラットにしようとしてみます、とりあえず Enumerator を使うのがお手軽そうです。

begin
    e1 = to_enum(:process,  [ "memcached", "-p", "12321" ])
    e2 = to_enum(:process, ["foo"])
    e3 = to_enum(:process, ["bar"])
    e1.next; e2.next; e3.next

    ....
ensure
    [e1, e2, e3].each do |e|
        begin
        e.next
        rescue StopIteration
        end
    end
end

フラットにはなりましたが、何をやっているのか大変謎になりました。イテレーション (繰替えし) してないのに next とか StopIteration とか出てきて意味不明ですし、処理が上下にちらばっているのが嫌な感じです。これならネストが増えるほうがマシな感じがします。

Enumerator をラップする

そこで Enumerator を適当にラップしてみます。以下のようなメソッドを定義します。

def guard_scope(&block)
	context = Object.new

	enums = []

	context.define_singleton_method(:guard) do |obj, name, *args|
		enum = obj.to_enum(name, *args)
		enums.unshift(enum)
		enum.next
	end

	ret = context.instance_eval(&block)

	enums.each do |enum|
		enum.next rescue StopIteration
	end

	ret
end

そして使う場合

guard_scope do
	g1 = guard(Kernel, :process,  [ "memcached", "-p", "12321" ])
	g2 = guard(Kernel, :process, ["foo"])
	g3 = guard(Kernel, :process, ["bar"])

	p :in_block
end

これでかなりマシになった気がします。

(ただ、これだと instance_eval を使っている関係でインスタンス変数のスコープが変わるのでよくありません)

継続を使う

Enumerator をラップすれば十分そうですが、どうも自分はメソッドの名前を Symbol にして渡して呼ぶのが好みではないので、以下のように書けたらいいなと思いました。

guard_scope do
	g1 = guard { process([ "memcached", "-p", "12321" ], &block)  }
	g2 = guard { process([ "foo" ], &block)  }
	g3 = guard { process([ "bar" ], &block)  }

	p :in_block
end

こうなると、Enumerator を使いにくいのでちょっと面倒です。継続を使って実装することにしました。spec コード付き

require 'continuation'
 
class Guard
	attr_reader :args
	attr_reader :called
	attr_reader :result
 
	def initialize(&before_finish)
		@args         = nil
		@guard_cc     = nil
		@block_cc     = nil
		@finish_cc     = nil
		@before_finish = before_finish
	end
 
	def finish(result=nil)
		@before_finish.call(self)
		cc = callcc {|cc| cc }
		if cc.is_a? Continuation
			@finish_cc = cc
			@result = result
			@called = true
			@block_cc.call(@result) # return finish block
		else
			cc
		end
	end
 
	private
 
	def return_guard
		@guard_cc.call(self)
	end
 
	def return_finish(ret)
		@finish_cc.call(ret)
	end
end
 
def guard_scope(&block)
	context = self
 
	guards = []
 
	current_guard = nil
 
	orig_guard = begin context.singleton_class.method(:guard) rescue NameError; nil end
	orig_block = begin context.singleton_class.method(:block) rescue NameError; nil end
 
	context.define_singleton_method(:guard) do |&b|
		cc = callcc {|cc| cc }
		if cc.is_a? Continuation
			current_guard = Guard.new do |g|
				current_guard = g
			end
			current_guard.instance_variable_set(:@guard_cc, cc)
 
			# Expect &block syntax and 
			# A method in block call block's lambda to get args
			# and return guard
			ret = b.call
 
			if current_guard.instance_variable_get(:@block_cc)
				# After finish of a guard
				# current_guard is set by finish (block passed to Guard.new)
				g = current_guard
				current_guard = nil
				g.send(:return_finish, ret)
			else
				raise "guard {} without calling &block"
			end
		else
			current_guard = nil
			cc
		end
	end
 
	context.define_singleton_method(:block) do
		count = 0
		lambda {|*args|
			raise "block is called more than once" if count > 0
			count += 1
			cc = callcc {|c| c }
			if cc.is_a? Continuation
				current_guard.instance_variable_set(:@args, args)
				current_guard.instance_variable_set(:@block_cc, cc)
				guards.unshift(current_guard)
				current_guard.send(:return_guard)
			else
				cc
			end
		}
	end
 
	block_value = context.instance_eval(&block)
 
	if orig_guard
		context.define_singleton_method(:guard, &orig_guard)
	else
		context.singleton_class.send(:remove_method, :guard)
	end
 
	if orig_block
		context.define_singleton_method(:block, &orig_block)
	else
		context.singleton_class.send(:remove_method, :block)
	end
 
	guards.each do |g|
		g.finish(nil) unless g.called
	end
 
	block_value
end

なんかだいぶ長くなりましたが、とりあえずこれで動くようになりました。

やってることはただの辻褄あわせで、一度ブロック付きで普通に呼んで、すぐ継続を作って guard {} を返してから、最後にまた保存した継続を実行しているだけです。

(この例は instance_eval を使わず、呼び出し元 self に一時的にメソッドを生やしているのでインスタンス変数のスコープが変わったりしないようになっています)

set_trace_func を使ったメソッド単位の Guard

set_trace_func を使えばメソッド単位の突入・脱出はとれるので、それで Scope::Guard 的に、先にブロックを登録しておくとスコープをはずれるときに自動実行するのを実装してみました。インターフェイス的には以下のようになります。

instance_eval do # 任意の適当なメソッド
    guard { p "called as leaving this method 1" }
    guard { p "called as leaving this method 2" }
end
module Guard
	@@guards = []
	def guard(&block)
		set_trace_func(lambda {|event, file, line, id, binding, klass|
			# p [event, file, line, id, binding, klass]
			case event
			when "call", "c-call"
				for g in @@guards
					g[:count] += 1
				end
			when "return", "c-return"
				out_of_scope = @@guards.each {|g|
					g[:count] -= 1
				}.select {|g| g[:count] < 0 }
 
				@@guards -= out_of_scope
 
				for g in out_of_scope
					g[:block].call
				end
				set_trace_func(nil) if @@guards.empty? && id != :set_trace_func
			end
		}) if @@guards.empty?
		@@guards << {
			block: block,
			count: 1
		}
	end
end
 
include Guard
 
require "rspec"
RSpec::Core::Runner.autorun
 
describe Guard do
	before do
		@events = []
	end
 
	it "should execute guard block as it is out of scope" do
		instance_eval do # new scope
			@events << :enter
			guard {  @events << :end1 }
			guard {  @events << :end2 }
			@events << :leave
		end
		expect(@events).to eq([
			:enter,
			:leave,
			:end1,
			:end2
		])
	end
 
	it "should works correctly with nested scope" do
		instance_eval do # new scope
			@events << :enter1
			guard {  @events << :end1_1 }
			guard {  @events << :end1_2 }
			instance_eval do
				@events << :enter2
				guard {  @events << :end2_1 }
				guard {  @events << :end2_2 }
				@events << :leave2
			end
			@events << :leave1
		end
		expect(@events).to eq([
			:enter1,
			:enter2,
			:leave2,
			:end2_1,
			:end2_2,
			:leave1,
			:end1_1,
			:end1_2,
		])
	end
end

割とシンプルに書けるんですが、set_trace_func が1個しか登録できないので、他のところで登録されると壊れるのと、スコープ単位ではなくメソッド単位にしかならないのが微妙なところです。良いところは、任意のメソッド内でいきなり guard を呼べるところで、これのためにブロックをつくる必要がない。

Scope::Guard 的なのの一番シンプルな実装

単に Scope::Guard 的に先にブロックを出たときの処理を登録するだけなら、もっと簡単に以下のように書けます。

イテレータ的なメソッドでなければ、ブロック引数をとる形式以外に、File.open のようにブロックを与えないときは自分で close するメソッドも提供されているので、大抵の場合はこれでもよさそうな気がします。

def guard_scope(&block)
	context = Object.new
	guards = []
	context.define_singleton_method(:guard) do |&b|
		guards << b
	end

	context.instance_eval(&block)
ensure
	guards.each {|g| g.call }
end

guard_scope do
	p :enter
	guard { p :end1_1 }
	guard { p :end1_2 }
	guard_scope do
		p :enter
		guard { p :end2_1 }
		guard { p :end2_2 }
		p :leave
	end
	p :leave
end

まとめ・感想

Guard オブジェクトのやりかたは無闇にインデントが増えず、なおかつ関連する処理を近くに配置して確実なコードを書けます。そんなこんなで自分は好きなのですが、簡単にできなそうなので、なんとかしていい感じにやる方法を考えてみました。

基本は Enumerator をラップして使うのがよさそう、あるいはもっと別の方法を考えたほうがよさそうです。継続を使う方法が一番カッコいいと思いますが、予期せぬことがありそうなので、できれば組込みクラスでやったほうがいいですね。なにかもっといい方法があれば教えてください。

  1. トップ
  2. tech
  3. Ruby でスコープをはずれたときに自動で何かをする

テストとかでやりたくなると思うけど、こんなくだらないことに gem とか使いたくないので、簡単に書ける方法をさがしてる。

class Foo
	def hello
		puts "Hello"
	end
end

みたいなクラスがあったとして、hello メソッドを一時的に上書きしたい (そして戻したい) とき

あるオブジェクトのメソッドだけを上書き

これは singleton class を使ったらすぐできるのでかんたん

foo = Foo.new

orig = foo.method(:hello) # 元のメソッドを呼びたいときだけ必要
foo.define_singleton_method(:hello) do
	puts "before hello"
	orig.call
	puts "after hello"
end

foo.hello #=>
# before hello
# Hello
# after hello

# 元に戻す
foo.singleton_class.send(:remove_method, :hello)

foo.hello #=>
# Hello

この方法は define_singleton_method だけでほぼ終わるし、元に戻すのも元のメソッドを保存しておく必要がないので (継承ツリーで上書きするので)、簡単

class << foo; alias ....; end

みたいなやりかたもあるけど alias でかぶったら嫌だし、alias の引数の順番覚えられないし好みではない

グローバルにあるクラスに属するメソッドを上書き

これはちょっと面倒な感じがする

# klass に定義された method メソッドをブロックで上書きする。
# ブロックには第1引数にオリジナルのメソッドが渡される
# 返り値は元に戻すための Proc インスタンス
def localize_method(klass, method, &block)
	unbound = klass.instance_method(method)
	klass.send(:define_method, method) do |*args|
		block.call(unbound.bind(self), *args)
	end
	lambda {
		klass.send(:define_method, method, unbound)
	}
end

みたいなのを定義して

foo = Foo.new
bar = Foo.new

puts "===="
foo.hello
puts "----"
bar.hello
# ====
# Hello
# ----
# Hello

release = localize_method(Foo, :hello) do |orig|
	puts "before hello"
	orig.call
	puts "after hello"
end

puts "===="
foo.hello
puts "----"
bar.hello
# ====
# before hello
# Hello
# after hello
# ----
# before hello
# Hello
# after hello

# 元に戻す
release.call

puts "===="
foo.hello
puts "----"
bar.hello
# ====
# Hello
# ----
# Hello

みたいに使えばいいのかな。もっと簡単にできたらいいけどできなそう…

  1. トップ
  2. tech
  3. Ruby でメソッドの一時的な上書き

見るたびにいちいちコードを読まないと安心して使えないライブラリなんてライブラリの価値がない。ライブラリ自体はブラックボックスになっていても、インターフェイスからその挙動を想像できるべきだ。

ライブラリを使っている側のソースを読む場合ではなく、あるライブラリを使おうとするときは、一度はライブラリの読んだほうがいいとは思うが、その目的は、そのライブラリが信頼できるかを計るためで、使いかたや、細かい挙動を確認するためではない。そして「どの程度信頼できるか」ということ、すなわちイケてるかイケてないかだけを覚えていればよい。

あるライブラリを使ったコードを読むとき、そのライブラリのソースまで毎回を読んで挙動を調べたりしなくてはいけないライブラリはインターフェイスがイケてない。ライブラリのインターフェイスが説明的になっていない。適切なインターフェイス設計がされていない。

適切なインターフェイス設計がされていなくても便利な「ライブラリ」は存在するけど、それはようするに便利なコピペ関数の寄せ集めである。あくまで書くとき便利なだけであって、読むときは害になる。

書くときに便利になるだけのものにはあまり意味がない。簡潔に書けることの目的はタイプ数を減らすことではなく、読むときに理解しやすくするためだ。

  1. トップ
  2. tech

ちょっとググってもいい感じのドキュメントが見つからず難儀したので、Perl でやるならこれで!! って感じで、コピペして使えるようなサンプルコードを書きました

社内でちょっと喋る必要があったので資料もあります (公開しても良さそうなように資料をつくってあります)

  1. トップ
  2. tech
  3. Perl で Thrift を扱うサンプルプログラム

なんかうまく AVR だと動かなかったりしたので、カっとなってテストを充実させた。(動くようになった)

とはいえ、コード上でテストだけ書いてもよくわからないので、可視化させながらやった。つまり時系列にバスの動きをシミュレーションできるようなコードを書いた。Ruby で cairo が結構かんたんに使えたので良かった。Time.now によってグラフを書いているので、多少ゆらぎがある。しかしこうすることで、意図せず状態を変えてしまっているのが一目でわかるようになったので大変役にたった。

例えば、i2cset のテストの場合

i2cget のテストの場合

見ての通り get のほうが複雑、というのも get するためにどのアドレスを get するかを書かないといけないから……

オシロスコープでの実際の波形

テストと同じデータの送受信 (対 AVR)。波形を綺麗にするために速度を 1kHz 程度に制限して (周辺処理に時間がかかるので最終的に400Hz程度だけど) sleep をちゃんと入れるようにしている。速度は下がるけど波形は綺麗になる。時間の単位さえ気にしなければ I2C 動いてる感がある。

i2cset

i2cget

AVR での repeated start condition in slave

AVR 向けだと repeated start condition を発行したとき AVR がハングする問題があって、解決方法がわからないので stop condition を常に挟むように実装を変えた。MPL115A2 だと普通に動くんだけど……

  1. トップ
  2. tech
  3. 続・GPIO (sysfs) を使ったソフトウェア I2C