XS(とC++)入門したい

XSを使ってみたい.ついでにC++も勉強したい.とりあえず今日はShibuya.pm #9 のXSトークを何個か見てみた.

PerlからXSの世界をながめてみる

まずは,PerlからXSの世界をながめてみる.整数値は一度文字列化すると文字列表現がキャッシュされる.Devel::PeekでXSレベルの構造体をダンプするとわかるよ.

コード
#!/usr/bin/env perl
use strict;
use warnings;
use Perl6::Say;
use Devel::Peek;

my $x = 100;
say Dump $x;
"$x\n"; # 文字列化
say Dump $x;
実行結果
SV = IV(0x80cebc) at 0x800b88
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,IOK,pIOK)
  IV = 100

SV = PVIV(0x802020) at 0x800b88
  REFCNT = 1
  FLAGS = (PADBUSY,PADMY,IOK,POK,pIOK,pPOK)
  IV = 100
  PV = 0x215c20 "100"\0 # このへんにキャッシュされてる
  CUR = 3
  LEN = 4

おおホントだ! このへんの構造を理解する必要がありそう.

簡単なXSもモジュールを書いてみる

何はともあれ,とりあえず動かしてみる.

モジュールテンプレートを作る
$ module-starter --module=MyHelloWorld --class=Module::Starter::XSimple
$ cd MyHelloWorld
XSファイルを修正する

いろいろファイルが生成されるけど,他には目もくれずlib/MyHelloWorld.xsを修正する.もともと作られていたのはごそっと消して以下のようにした.

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

MODULE = MyHelloWorld       PACKAGE = MyHelloWorld

char *hello()
    CODE:
        RETVAL = "Hello, World!";
    OUTPUT:
        RETVAL

typemapといる仕組みがあるらしいので,char*は返せそうなので返してみてる.

ビルド
$ perl Build.PL
$ ./Build

Module::Buildはあんまり使わないので新鮮.

実行
$ cd ..
$ perl -Mblib -MMyHelloWorld -MPerl6::Say -e 'say MyHelloWorld::hello()'
Hello, World!

おお,たしかにPerlからCで書いたコードを呼びだせた! けど,単に文字列返してるだけで何のありがたみもないぜ…

とりあえず

  • 書いたら動いた.けど,何にもできない.
  • SVとかPVIVとかそのへんの話がほとんどわかってないので,perldoc perlxstutなんかのドキュメントを読んで勉強する
  • そもそも,C/C++で何がしたいかとかがなくて指針がない.目標を決めて勉強する
    • C++の勉強を平行してやりたいのでC++の流行をチェックして何ができそうか調べてみよう
  • 最終的になんかひとつXSモジュールを書く

XS勉強するだけが目的だと,どっちに向って走ればいいのかわかんないかんじなので,C/C++の世界をいろいろウォッチしてみて,おもしろそうなネタがないかさがしてみよう.

XSモジュール用のModule::Setupのflavorが公開されてた

比較的モダンなXSモジュールのテンプレートジェネレータがModule::Starter::XSimpleしかなくて,ちょっとがっかり*1していたら,ついさっきModule::Setup用のXSなFlavorが公開されてた!

http://blog.clouder.jp/archives/001094.html

個人的にすばらしいタイミングでびっくり.Module::Installベースだからわかりやくてよいなぁ.clouderさん++.

Flavor形式で一発インストール余裕でした.

*1:Module::Starter::XSimpleはModule::InstallじゃなくてModule::Buildを使う.Module::Buildよくわからない<>

ファイルの変更を監視してmake

omakeを使うと楽ちんにファイルの変更を監視して自動でmakeできて便利! texで論文書くときとかには非常に役に立ちますね.(参考: OMake つかって LaTeX コンパイルしたら簡単すぎて身長が5cm伸びた - 日記を書く [・w・] はやみずさん)

でも,omakeだと,いまいち,うまくdvipdfmxにオプションがわたんなかったり,これまで育ててきたMakefileを使う方法がよくわかんなかったりして,ちょっと不満が残る感じに…

とりあえず,うちとしては,すごい依存解決とかはいらないので,ファイル監視して変更されてたらmakeしてくれればOKなんだけど… って,それFile::Modifiedでできるよ!

perl watch.pl make *.tex

とかで実行しとくと*.texファイルを監視して変更されてたらmakeが起動します.コードは以下.

ちょー車輪っぽい.*1はじめはPOEのイベントディスパッチループ上でdelayさせてたんだけど,処理してるイベントがwatchだけなんでPOEってる意味はないので普通のループにした.あいかわらずPOEはよくわかってないので,ちゃんと勉強したいなぁ.

論文の提出は1週間ほど前に終わったのですが,なぜか風呂に入ってたら思いついたので書いてみた.

*1:Webフレームワークのサーバスクリプトでよくありますね.

JPerl Advent Calendar 2008にTipsを書きました

12月17日担当のantipopさんからまわってきていたJPerl Advent Calendar 2008の18日目を書きました.(http://d.hatena.ne.jp/tokuhirom/20081216/1229387324)

Perlはファイルの中身を全部読み込むのにやたらイディオムがあったりするのでそれについて書いてみました.

正規表現のoオプションにPerlの暗黒面を見た

Perlの正規表現のoオプションてご存じでしょうか? うちはあまり使ったことがなかったのですが,効率のよい正規表現処理を行うときには便利なオプションです.

oオプションを使うと,その正規表現のコンパイルが1回しか行われなくなります.たとえば,正規表現の評価が繰り返しのなかで現れると,コンパイルが何度も行われて効率が悪くなります.そういったときにoオプションを付与しておけば,正規表現コンパイルのオーバーヘッドを最小限にとどめることができます.

chomp(my $pattern = <>);
for my $n (0..100000000000) { # すごく回数の多いループ
    # oをつければ正規表現がコンパイルされるのははじめの1回だけなので安心
    if ($n =~ m/^$pattern$/o) {
       say "match!";
    }
}

正規表現が静的な場合は,Perlはこのような最適化を自動で行うのですが,例のように正規表現が動的に決定する場合は,このような最適化ができないので,明示的に指定する必要があるんですね.

しかし,このoオプションは使いどこををまちがえると大変めんどうなバグの原因になります.

例えば,次のようなコードで正規表現のoオプションを使ってみたとします.このmatch関数はすごく何度も呼ばれるのでoオプションをつけました.そのあと,match関数を使ったコードでマッチングをしています.

sub match {
    my ($pattern, $text) = @_;

    # 繰り返しの中で使うのでoオプションをつけよう!
    return $text =~ m/^$pattern$/o;
}                    
                        
# すべてマッチする      
for my $text (qw(vim vim vim vim vim)) {
    if (match('vim', $text)) {
        say "match: vim";
    }                   
    else {
        say "not match: vim";
    }
}

# すべてマッチする?
for my $text (qw(emacs emacs emacs emacs emacs)) {
    if (match('emacs', $text)) {
        say "match: emacs";
    }
    else {
        say "not match: emacs";
    }
}

このプログラムの実行結果は,次のようになります.

match: vim
match: vim
match: vim
match: vim
match: vim
not match: emacs
not match: emacs
not match: emacs
not match: emacs
not match: emacs

なぜか後半のマッチングがすべて失敗してしまいます!ちゃんと引数に'emacs'を指定しているのに!一回目に正規表現をセットしたときはうまく動くのに,二回目以降はなぜかうまく動かない関数ができました.おそろしい.

これはoオプションが原因で起こります.どうやら,oオプションつけた正規表現は,一度コンパイルされたら,スクリプトの実行が終わるまで決して変更されることがなくなるようです.*1 なので,いくら引数で値を渡しても一度コンパイルされた正規表現はかわりません.関数のスコープとか無視してきます.まじでこわいですね.

コードをみても一見まちがいがわからないし,一回目の実行はうまくいったりするので,これのがらみのバグはかなりやな感じです.しかも,oオプションを削ったら普通に動くようになったりするので,かなりショックです.かなりショックでした.

結局何が言いたかったかというと,Perlの闇は深すぎて,油断するとすぐに真っ黒に染められてしまう*2ので,全力で取り扱い注意ということですね.

*1:こまかい条件はわからない

*2:主にコードとココロが

より自然にRubyっぽくリストを書けるモジュールを書いた

PerlのARRAYをRubyのArrayっぽく扱うためのモジュールがすでに三個くらいあって大人気ですね.

こういったモジュールを使うと,

my @elems = map { $_ ** 2 } grep { $_ % 2 == 0 } (0..100);
my $sum = 0;
for my $n (@elems) {
  $sum += $n;
}

とか書いていたコードが,

# List::Rubyishの場合
my $sum = List::Rubyish->new([0..100])
    ->grep(sub{ $_ % 2 == 0})
    ->map (sub{ $_ ** 2    })
    ->reduce(sub { $_[0] + $_[1] });

みたいな感じのメソッドチェーンで書けるようになってオシャレですね.*1

しかし,こういうこと言ってると,

「List::Rubyish->new てなんなの.ちょーかっこわるいよ.リテラルでメソッド呼べるRuby最高!」

とか言ってPerlをDISりはじめるひとが現れるのは必至です.そこで, RubyLikeなメソッドをARRAYでautoboxして使える,autobox::UNIVERSAL::Listを書きました.

autobox::UNIVERSAL::List を使うと,RubyLikeなメソッドがARRAYにautoboxされます.すると,リテラルから直接メソッドを呼ぶことが可能になるので,上の例が

use autobox::UNIVERSAL::List;
my $sum = [0..100]->grep(sub{ $_ % 2 == 0})
                  ->map (sub{ $_ ** 2    })
                  ->reduce(sub { $_[0] + $_[1] });

と書けるようになります.これでRubyistも何もいえないですね!

デフォルトでは,List::Rubyshが裏で使われるのですが,

use autobox::UNIVERSAL::List
    impl_class => "List::RubyLike";
my $sum = [0..100]->grep(sub{ $_ % 2 == 0})
                  ->map (sub{ $_ ** 2    })
                  ->reduce(sub { $_[0] + $_[1] });

とかすると,List::RubyLikeを使うこともできるので,List::Rubyshがふるまいが気に入らなくても安心です.

impl_classには,ARRAYから生成できてかつ,結果をARRAYを返すことのできるクラスならば,なんでも指定できます.たぶん.なので,RubyLikeなArrayのメソッド実装以外のものを組み合わせることが簡単にできちゃったりするはずです.あんまりためしてないので,不安です.

ARRAY生成のためのコンストラクタや結果をARRAYで返す部分は, useするときに指定することもできます.

use autobox::UNIVERSAL::List
    impl_class => "List::Enumerator::Array",
    new        => sub { List::Enumerator::Array->new(array => shift) },
    to_a       => sub { shift->to_a };
# my $sum = [0..100]->grep(sub{ $_ % 2 == 0})
#                  ->map (sub{ $_ ** 2    })
#                  ->reduce(sub { $_[0] + $_[1] });
# ただし,List::Enumerator::Array は 多くのメソッドをList::Enumerator::Roleから
# 継承してるのでうまく動かない

制限としては,impl_classが何か別のクラスを継承している場合はうまくいかないので,List::Enumerator::Arrayを使うことはできません.ここはどうにかしたいのですが,クラスの持っているメソッド名を継承元もたどって全部とれたらなんとかできそうです.

さらに,overloadされた演算子もうまく動きません.リストリテラルの演算子のoverloadとか怖すぎます.というかできるのかもよく知りません.

というわけで,use autobox::UNIVERSAL::Listの紹介でした.正直,わざわざautoboxにする必要がないので,実用性はあんまりな気がしますが,おしゃれなスクリプトをクールに決めたい人はおもむろにuseするといいかもしれません.

*1:例がいまいちすぎるなー.おしゃれな例ないかなー