Perl 5.19.9 で実装された signatures の構文をためしてみる - tokuhirom blog を見てそんなのできたのか〜と思ったので、いろいろ試してみた。なんとこれは、今までになく直感的に引数が書けてしまう革命的構文です。

use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";

use feature 'lexical_subs';
no warnings "experimental::lexical_subs";

# 引数の数チェックをしてくれる。便利
eval {
	say "check length of arguments";
	state sub hello ($foo) {
		say "hello $foo";
	}


	hello();
	#=> Too few arguments for subroutine at pl.pl line 9.
}; if ($@) { warn $@ }

# デフォルト値も普通に書けます
eval {
	say "default value";
	state sub hello ($foo="world") {
		say "hello $foo";
	}


	hello();
	#=> "hello world"

	hello("yunotti");
	#=> "hello yunotti"
}; if ($@) { warn $@ }

# デフォルト値に式も書けます。Ruby なんかでも書けますね。引数として指定されない場合だけ氷菓されます
eval {
	say "expression in default value (evaluated same as Ruby)";
	my $i = 0;
	state sub hello ($foo=$i++) {
		say "hello $foo";
	}


	hello();
	#=> hello 0

	hello();
	#=> hello 1

	hello("yunotti");
	#=> "hello yunotti"

	hello();
	#=> hello 2
}; if ($@) { warn $@ }

# @foo を指定したら可変引数にできます。%hash とかもできます。
eval {
	say "variable length arguments";
	state sub hello ($foo, @rest) {
		say "hello $foo and " . join(", ", @rest);
	}


	hello("yunotti", "miyachan", "sae", "hiro");
	#=> hello yunotti and miyachan, sae, hiro
}; if ($@) { warn $@ }

# @_ の挙動は今までどおり変数渡しです。
eval {
	say 'with @_: @_ is passed by variable (same as prev perls)';
	state sub hello ($foo, $bar) {
		$foo = 'xxx';
		$_[1] = 'xxx';
	}


	my $foo = 'foo';
	my $bar = 'bar';
	hello($foo, $bar);
	say "$foo, $bar"; #=> foo, xxx

}; if ($@) { warn $@ }

ただ引数の名前とかは外からとることができない。せっかく構文に組込まれたのなら、とれてもよさそうだなと思った。すこしコード追ってみたけど、基本、今までで同じようなコードが内部的に生成されるだけっぽい。サブルーチンリファレンスに附属する形でなんかメタデータを入れれたらいいんだけど、よくわからなかった。

あと、Smart::Args みたいなのは使えるかと思って試してみたけど、既存コードと同じ感じなので、当然普通に使える。ただ、signatures を活用した感じにはできなそう。うまいやりかたあるのかな。

use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";

use feature 'lexical_subs';
no warnings "experimental::lexical_subs";


{
	# Smart::Args は普通に使える
	use Smart::Args;
	{
		# % を書けば呼ばれる時点でハッシュかどうかのチェックは入る
		state sub hello (%) {
			args(my $foo);
			say "hello $foo";
		}

		hello(foo => "foobar");
		eval {
			hello(1);
		}; if ($@) { warn $@ }; #=> Odd name/value argument for subroutine at pl.pl line 20.
	};

	{
		# % を書かなければ今までどおり Smart::Args 側でエラる
		state sub hello {
			args(my $foo);
			say "hello $foo";
		}
		eval {
			hello(1);
		}; if ($@) { warn $@ }; #=> Odd number of elements in anonymous hash at lib/site_perl/5.19.9/Smart/Args.pm line 39.
	}
};

{
	# せっかくなら signatures を活用したい感あるけどいい感じにならない (引数名を2度書かないといけない)

	use PadWalker qw/var_name/;
	sub validate ($var, $rule) {
		my $name = var_name(1, \$_[0]) =~ s{^\$}{}r;
		require Smart::Args;
		$_[0] = Smart::Args::_validate_by_rule($_[0], 1, $name, $_[1]);
	}

	{
		state sub hello ($foo) {
			validate($foo, { isa => 'Int' });
			say "hello $foo";
		}
		eval {
			hello("xxx");
		}; if ($@) { warn $@ }; #=> Validation failed for 'Int' with value xxx
	}
};
  1. トップ
  2. tech
  3. perl 5.19.9 の signatures 構文 (普通に引数を書ける構文) を試す

bare metal という言葉を最近知って、おもしろそうだなあと思ったので Raspberry Pi 上で試してみた。bare metal とは OS レスで直接動かす (つまりリッチなマイコン的に使う) ことのようです。bare metal で動かすだけだと目標が低い感じなので、まず mruby を動かすところまでやろう、という感じです。

yamanekko さんというかたが既にやって日本語で書いているので、それを見てコピペしながらやってみつつという感じです。ちょいちょいうまくいかなかったのでかなりハマった……

ARM クロスコンパイル環境の構築

https://launchpad.net/gcc-arm-embedded にあるバイナリをインストールするのが一番良い。これを入れれば以下にあるような fpu 問題にも悩まない。

大変めんどう。ググるといろいろでてくるけど、今流行りの全部うまいことやってくれる感じのスクリプトがあったので試してみた。

https://github.com/jsnyder/arm-eabi-toolchain

git clone git@github.com:jsnyder/arm-eabi-toolchain.git
cd arm-eabi-toolchain
make install-cross

大変時間がかかる。Makefile を開いて、--enable-languages="c,c++" となってるところから c++ を消したほうがいいっぽい。使わないし…

入るライブラリ

  • arm-none-eabi-
    • gcc
    • binutils (objdump とか)
    • gdb
  • GMP
    • 数値演算ライブラリ
  • MPFR
    • 浮動小数点ライブラリ
  • MPC
    • MPFR ベースのさらに複雑な浮動小数点ライブラリ
  • newlib
    • 組み込み環境用のライブラリ。 libc とかを提供

少し古いバージョンが入る

FPU 問題

最初

arm-none-eabi-ld: error: main.elf uses VFP register arguments, /Users/cho45/arm-cs-tools/arm-none-eabi/lib//libc.a(lib_a-errno.o) does not

とか出て ld が通らなかった。

yamanekko さんのレポジトリにある Makefile だと fpu ライブラリへのパスが書いてあるけど、上のスクリプトでクロスコンパイル環境を構築してもでてこない……

gcc の configure オプションか何かなのかもしれないけど、時間がかかるしむずかしいので諦めた。

よくわからなすぎるのでググったらARM EABIでの浮動小数点演算というが参考になった。

結局 -mfloat-abi=soft にして完全にソフトウェア命令しか出さないようにしたら動くようになった。-mfloat-abi=soft=softfp だと浮動小数点演算が走ったタイミングで死んだりしてよくわからない。

オプションの種類は arm-none-eabi-gcc --target-help で見ることができる。-mfpu=vfp のところもいろいろ種類があるけどよくわからない。

未だ完全には解決してない。

リンカスクリプト

memmap というファイルになってる。面倒だった。間違えるといろいろよくわかないことになる。今回遭遇したのは

  • .ARM.exidx を定義してなかったせいでおこられる
    • C++ 用のセクションらしいけど 特定の関数を使おうとすると必要っぽい
    • 定義の順番がおかしいとできるバイナリがデカくなったり、動かなくなったりする
  • アラインメントがおかしかったせい?で newlib の mALLOc が無限ループする
    • 当然ヒープを実装せず使わないなら再現しないので簡単なLチカだと気付かない

newlib が要求してくるシステムコール類は syscalls.c でモック的に定義されている(コピペしたのものをクリーンアップした)。だいたいはどうでもいい関数だけど、_sbrk が大変重要で、これによってヒープが実現されている。

メモリ割当のマッピングとかはリンカスクリプトで定義したものを vectors.s (アセンブラ) で C 側から見れるようにして、それを使ってヒープとかの実装をしているみたい。

gdb でシミュレーションデバッグ

.gdbinit に書いてあるけど

file main.elf
target sim
load

とすることで、gdb 上でシミュレーションして動かすことができるので、周辺機器が絡まない、とりあえずのデバッグはこれでも十分できる。

ただ、一部の演算、確認したのは % (余剰)で変なエラーがでておかしい。

実際やった手順

mruby なしでLチカ

まず mruby なしでLチカができないとどうしようもないので、まずそこまでできるプロジェクトツリーを作って動くことを確認するまでやった。

https://github.com/cho45/raspberrypi-bare-metal

この過程だけでも上記の通りかなりハマった……

mruby ありでLチカ

次に mruby ありでLチカに挑戦した。yamanekko さんのmruby-rs-led を参考に、Linux の gpio 的な GPIO module gem を実装した。

GPIO のアドレスとかはデータシート記載のものを手動で変換せずにできるだけそのまま書いておきたいと思うので、そうしてる。バスアドレスというのと、物理アドレスというのがあって、データシートにはバスアドレスで書いてあって、しかし実際使うのは物理アドレスなので、変換 (固定値) が必要みたい。

これによって以下のように書けるように (完全にちゃんと動いているか微妙だけど) なった。Ruby にすると一気に簡単になった感じがして落差がウケます。ほぼ Linux 上で適当にラッパー書いて書くのと同じコードになった。うまくラップできれば主要コードは Ruby レベルでテストが書けてよさそう。

pin = 16

GPIO.direction(pin, :low)
bool = false
loop do
	bool = !bool
	GPIO.write(pin, bool)
	10000.times { }
end

しかしここまで動かすのも大変で、何時間もハマった。ここでハマったのは特にリンカスクリプトでアラインメントがおかしかったせいで、mrb_open() がそもそも返ってこないというのがつらかった。

gdb で追ってもなぜか newlib の中で無限ループにになるので意味不明だった。

そしてそれがようやくなおったと思っても mrb_open() の最中に浮動小数点演算もしていて、ここでハングするということが起きてつらかった。最終的には上記の通り、とりあえず常にソフトウェアにしてなんとか動かした。

sleep の実装を書いてないので Ruby 側でビジーループしてる。16ピンにしてるのは、Raspberry Pi の基板上に実装済みの LED だからで、デバッグ用に新規配線をしなくてすむからです。

sleep の実装

さすがにビジーループをRuby側でやるのはダサいので次に sleep を実装した。

system timer と arm timer というのがあるけど、今回は system timer を使った。arm timer はシステムの状態によってクロックが変わるみたい。system timer は独立したクロックを持ってるようだ。

ただ、肝心の system timer clock がよくわからなかった。試行錯誤したかんじだと、1Mhz ぐらいで動いてる。分周比設定とかがなくて謎。要追試

Cレベルではビジーループしてる。うまくCPUスリープさせたいけど、まだよくわかってない。

mruby でハマったところ

mruby ではあまりハマらなかった。mrbgem 作るのは大変簡単になっていておもてなし感がある。

ただ、build_conf.rb で gem を指定せずにビルドすると、あとから gem を指定しても、clean するまで反映されないのが罠ぽかった。深くおってないけど、DISABLE_GEMS が永続されてしまうぽい?

この件は再現させて1行パッチ送ったらマージされたのでたぶん大丈夫です。build_config.rb を書きかえたら .c ソースのリビルドが走るはず…

レポジトリ

これも習作だけど別レポジトリにおいてる

課題点

  • fpu の問題の解決
  • GPIO モジュールの拡張
    • TIMER や割込みなどを使えるように
    • ハードウェア I2C SPI の実装
    • bit banging での I2C 実装
  • JTAG を試す
  • ブートローダーを使うように
    • SDカードいちいち抜き差ししてるとつらいので

感想

mruby は夢が広がる。mruby で OS とか作ったらかっこよさそう。

  1. トップ
  2. tech
  3. mruby を Raspberry Pi 上で bare metal で動かすまで

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 でスコープをはずれたときに自動で何かをする