ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

Perl の Class::Accessor::Lvalue::Fast モジュールの使い方とか実装とか

Class::Accessor::Lvalue::Fast モジュールを使っているコードをしばしば業務で目にしつつも、ちゃんと体系だって理解してなかったので調べてみた。 自分用メモ程度の内容なので詳細はドキュメント参照ということで

Class::Accessor 系のモジュール

Class::Accessor::Lvalue::Fast クラスは、Class::Accessor クラスを拡張するもの。 ここでは、Class::Accessor クラスとそれを拡張するようなクラス群を Class::Accessor 系のクラスと呼ぶことにして、それらのクラスを提供するモジュールについて説明する (Class::Accessor::Lvalue::Fast モジュールに関連するもののみ)。

Class::Accessor モジュール

基本となるのは Class::Accessor クラス。 このクラスを親クラスとするサブクラスを作ると、サブクラスには Class::Accessor クラスから以下のものが提供される。

  • ハッシュリファレンスベースのインスタンスを生成するクラスおよびインスタンスメソッド new
  • インスタンスメソッド get, set
  • アクセサメソッドを作るためのクラスメソッド mk_accessors, mk_ro_accessors, mk_wo_accessors

new メソッドはハッシュリファレンスを引数に取ることができ、ハッシュリファレンスがある場合はそのコピーがインスタンスのベースになる。 引数がない場合は、空ハッシュのリファレンスをベースにインスタンスが生成される。

インスタンスメソッド set は、指定されたキーを使ってインスタンスのハッシュから値を取り出し、それを返す。 インスタンスメソッド get は、指定されたキーを使ってインスタンスのハッシュに値を格納する。

mk_accessor, mk_ro_accessors, mk_wo_accessors はクラスメソッドであり、このメソッドを実行することでアクセサメソッドとなるインスタンスメソッドが定義される。 引数として、生成されるアクセサメソッドの名前となる文字列のリストを取る。 さらにその名前がインスタンスのハッシュのキーにもなる。 mk_ro_accessors は読み取り専用のアクセサ、mk_wo_accessors は書き込み専用のアクセサを定義する。 定義されるアクセサは以下の形式になる。

$self->accessor_name( $key, @values )

mk_accessor メソッドの引数に渡した文字列の値が accessor_name になる。 @values が空の場合 getter となり、@values が 1 つ以上の値を持つ場合は setter になる。 @values として 2 つ以上の値を渡した場合は、それらの値を要素としてもつ配列へのリファレンスが値としてセットされる。 なお、このアクセサメソッドは make_accessor, make_ro_accessor, make_wo_accessor メソッドによって生成されるものであり、これらのメソッドは単に get または set メソッドを呼び出すようなメソッドを生成する。

サブクラスで set, get メソッドや make_accessor, make_ro_accessor, make_wo_accessor メソッドをオーバーライドすることで、定義されるアクセサメソッドの動作を変更することができる。

Class::Accessor::Fast モジュール

Class::Accessor::Fast クラスは Class::Accessor クラスのサブクラス。 make_accessor, make_ro_accessor, make_wo_accessor メソッドをオーバーライドし、get, set メソッドを使わずに、直接インスタンスのハッシュの値を読み書きするようなアクセサを生成する。

アクセサの動作が多少速くなるが、get, set メソッドのオーバーライドによる動作の変更ができなくなるという欠点もある。

Class::Accessor::Lvalue モジュール

Class::Accessor::Lvalue クラスは Class::Accessor クラスのサブクラス。 make_accessor, make_ro_accessor, make_wo_accessor メソッドをオーバーライドし、アクセサメソッドに代入できるような文を書けるようになる。

# __PACKAGE__->mk_accessor( 'accessor' ) で作られたアクセサメソッドに対して以下のように代入の式を書ける
$obj->accessor = 120;

実装としては、アクセサメソッドを lvalue サブルーチンにしている。 get, set メソッドを使うために tie 関数を使っているので動作が遅くなっているようで、get, set メソッドを使う必要がない場合は Class::Accessor::Lvalue::Fast を使うことを公式的に薦めている。 tie 関数や lvalue サブルーチンに関しては後述。

Class::Accessor::Lvalue::Fast モジュール

Class::Accessor::Lvalue::Fast クラスは Class::Accessor::Fast クラスのサブクラス。 Class::Accessor::Lvalue クラスと同様に make_accessor, make_ro_accessor, make_wo_accessor メソッドをオーバーライドし、アクセサメソッドに代入できるような文を書けるようになる。 ただし、Class::Accessor::Fast クラスと同様に get, set メソッドを介さずに直接インスタンスのハッシュを操作するアクセサメソッドが定義されるので、Class::Accessor::Lvalue クラスを使った場合よりもアクセサメソッドの動作が高速になる。

内部実装の話

内部実装で使われているものたち。

lvalue サブルーチン

サブルーチンの定義時に lvalue 属性をつけてやることで、(呼び出し側から見ると) メソッドへの代入ができるようになる、というもの。 Perl 5.6.0 からサポートされてるらしい。 便利っぽいけど下記のように書かれているので、あまりがしがし使わないほうがいいのかも。

Lvalue subroutines are still experimental and the implementation may change in future versions of Perl.

Lvalue subroutines (perlsub - perldoc.perl.org)

なお、Want モジュール を使うことで、lvalue 属性がつけられたメソッドへの代入としての呼び出しがされたのかそうでないのかを知ることができる。

tie 関数

tie()とはなにか?

ずばり、「オブジェクトではない、perlの組み込みデータタイプを裏でオブジェクト化する仕組み」のことです。それがなぜtieと呼ばれるかと言えば、変数を指定されたクラスに結びつけるから、です。

perl - tie()って何をtieするの? (404 Blog Not Found)

Class::Accessor::Lvalue モジュールでは、lvalue サブルーチンの戻り値として tie された変数を返すことで、アクセサメソッドを lvalue サブルーチンにしつつ、get, set メソッドを使ってハッシュを操作するということを実現している。

「Math::BigInt: couldn't load specified math lib(s)」 を解消する (Perl)

Math::BigInt 関係のモジュールを使用していると以下の警告 (エラー?) に遭遇。 必要なライブラリがないから Math::BigInt::Calc モジュール を代わりに使うよ、ってことみたいですね。

Math::BigInt: couldn't load specified math lib(s), fallback to Math::BigInt::Calc

Web 上を調べていると、Crypt::DH モジュール を使う際に上記警告に遭遇している人が多そうな感じでした。 すべての場合にそうだとは限りませんが、うちの場合は Math::BigInt::GMP モジュール をインストールすることで警告を解消できました。

Math::BigInt::GMP モジュールのインストール

Perl の Math::BigInt::GMP モジュールは libgmp を必要としているのでまずそれをインストールします。 Debian squeeze なら以下コマンドでインストールできます。

$ sudo apt-get install libgmp3-dev

その後、cpanm などを使って Math::BigInt::GMP をインストールします。

$ cpanm --verbose Math::BigInt::GMP

これで警告は消えました。

PerlMagick (Image::Magick) のインストール (Debian squeeze + perlbrew 環境)

ImageMagickPerl 用インターフェイスである PerlMagick (Image::Magick モジュール) のインストールがなかなかうまくいかなかったので、メモしておきます。

環境

  • Debian squeeze (6.0.4)
  • Perl 5.8.8 (Perlbrew 0.42 でインストール)

背景

最初は apt-get で ImageMagick 関連の必要そうなパッケージをインストールして、cpanm で PerlMagick (Image::Magick) をインストールしようかとしました。

$ cpanm Image::Magick

しかしなぜかうまく行かず。 Web 上を調べると同様のことをしようとして失敗している人が結構居て、PerlMagick の Makefile.PL を書き換えて ImageMagick のライブラリや include のパスを追加すればいい、というような情報もありましたが、そういうことをやってみても成功しませんでした。 PerlMagick のバージョンは ImageMagick に合わせないとダメらしいので、cpanm でインストールする PerlMagick のバージョンを指定したりもしましたが結局だめでした。

エラーメッセージを見ても何がダメなのかよくわからない、という状態。

しょうがないのでソースからインストール

多分何か足りないものがあるんだと思いますが、何が足りないのかよくわかんないので ImageMagick と PerlMagick の両方ともソースからビルドすることにしました。

ImageMagick のインストール

公式のインストールガイドは下記ページ。

最新の ImageMagick 6.7.6-5 をインストールしました。 インストールガイドに従えば特に問題なくインストールできると思います。

なお、apt-get でインストールされた ImageMagick 関連のライブラリとソースからビルドしてインストールしたものは共存できるはずですが、ややこしいので apt-get でインストールされた方はあらかじめアンインストールしておきました。

PerlMagick のインストール

公式のインストールガイドは下記ページ、またはソースディレクトリ内の README.txt を読むといいでしょう。

まず、ImageMagick のバージョンに合わせた PerlMagick (例えば ImageMagick 6.7.6 に対しては PerlMagick 6.76) のソースコードをダウンロードして、展開します。 今回の場合は、PerlMagick 6.76 をダウンロードしました。

次に、展開後の PerlMagick のディレクトリに移動して Makefile.PL のパスのところに、自分の環境に合わせた MagickCore ライブラリへのパス情報を追加 します。 インストールガイドだとそれだけでいいはずなのですが、Makefile.PL を見ると、ビルドオプションとして "-lperl" というのがついていて、libperl も必要とされている様子があります。 実際のところ libperl が必要なのかどうか疑問ですが、libperl へのパスも追加することにします。 うちの環境だと、

に libperl がありますので、

をパスに追加しました。

後はガイドどおりに下記コマンドを実行することでインストール完了です。

$ perl Makefile.PL
$ make
$ make install

ただし、perl のインストール時に ccflags として -fPIC を指定していないと以下のようなエラーが出てしまうと思います。

$ make
Skip blib/lib/Image/Magick.pm (unchanged)
cc -c  -I/usr/local/include/ImageMagick -I../ -I.. -pthread -I/usr/include/cairo -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include -I/usr/include/pixman-1 -I/usr/include/freetype2 -I/usr/include/libpng12 -pthread -I/usr/include/pango-1.0 -I/usr/include/freetype2 -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include -I/usr/include/graphviz -I/usr/include/freetype2 -I/usr/include/libxml2 -I"/usr/include/ImageMagick" -fno-strict-aliasing -pipe -Wdeclaration-after-statement -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -fopenmp -g -O2 -Wall -pthread -O2   -DVERSION=\"6.76\" -DXS_VERSION=\"6.76\" -fpic "-I/home/nobuoka/perl5/perlbrew/perls/perl-5.8.8/lib/5.8.8/x86_64-linux/CORE"  -D_LARGE_FILES=1 -DHAVE_CONFIG_H Magick.c
Running Mkbootstrap for Image::Magick ()
chmod 644 Magick.bs
rm -f blib/arch/auto/Image/Magick/Magick.so
LD_RUN_PATH="/usr/local/lib" cc  -L/usr/local/lib -lMagickCore -L../magick/.libs -lMagickCore -shared -L/usr/local/lib -L/usr/lib Magick.o  -o blib/arch/auto/Image/Magick/Magick.so 	\
	   -L/usr/local/lib -L/home/nobuoka/perl5/perlbrew/perls/perl-5.8.8/lib/5.8.8/x86_64-linux/CORE -lMagickCore -lMagickCore -lperl -lm  	\
	  
/usr/bin/ld: /home/nobuoka/perl5/perlbrew/perls/perl-5.8.8/lib/5.8.8/x86_64-linux/CORE/libperl.a(perl.o): relocation R_X86_64_32 against `.rodata.str1.1' can not be used when making a shared object; recompile with -fPIC
/home/nobuoka/perl5/perlbrew/perls/perl-5.8.8/lib/5.8.8/x86_64-linux/CORE/libperl.a: could not read symbols: Bad value
collect2: ld returned 1 exit status
make: *** [blib/arch/auto/Image/Magick/Magick.so] エラー 1

その場合は、以下のように -fPIC オプションを指定して Perl をビルドすれば良いようです。

$ perlbrew install perl-5.8.8 -A ccflags=-fPIC

libperl っているのだろうか

結局のところ PerlMagick に libperl っているんですかね。 なくても問題ないような気がします。

github にアップロードした README.pod が文字化けした問題 (POD の encoding コマンド)

github では、pod やら rdoc やらいくつかのマークアップ形式を理解して、自動的に README ファイルを HTML に変換してくれます。 で、Perl のプロジェクトを作っていたので POD で README ファイルを書いていたのですが、何も考えずにアップロードすると下のように文字化けしてしまいました。

全然知らなかったんですが、pod には encoding コマンドがあって、それで POD ファイルの文字エンコーディングを指定できるのですね。

この encoding コマンドを使って POD ファイルの文字エンコーディングを指定することで、github にアップロードした際にも文字化けせずに表示されるようになりました。

US-ASCII または Latain-1 以外のエンコーディングで POD を書く場合は encoding コマンドでエンコーディングを指定するようにするのが良さそうです。