Hatena::ブログ(Diary)

Yet Another Hackadelic

2008-06-06 悩むより前進あるのみ

XRI vs URI

これ実は最近のホットな話題みたいですね。

がニュース系の記事で、

ここらへんが W3C TAG の意見。

=nat さんの反論はこちら

これはどの辺りに見解の相違があるのか、また何故こういわれるのか、

XRI 自体の存在の有用性だとか僕にはまだまだ分からない点が多すぎるんですよねぇ。

=nat さんにはこの辺りの話をして貰いたいなー。

ちなみに個人的には XRDS はとても気に入ってます。XRDS-Simple 辺りがちょうど良い感じ。

プログラミングすると言うこと

余りこうした事をブログに書かないようにしていたんですが、最近嬉しい事があったので勢い余って書いて見ます。

Perl is unDead と言う YAPC Asia 2008 での Michael Schwern のプレゼンから、日本の Perl コミュニティが Perl-users.jp - 日本のPerlユーザのためのハブサイト を立ち上げたのは記憶に新しい所で、その流れを受けて no title なんかも出てきて、色んな人が日々コードとその解説を投稿していたりと、最近 Perl コミュニティが対外的にアピールする機会が増えていて、やっぱり嬉しいです。

この流れの早さ、コミュニティ一体になってガンガン進んでいくのが日本のPerlコミュニティの良い所で、IRC上であーだこーだ言いながらサイトにコンテンツがどんどん追加されていく様を見て、このコミュニティはやっぱりいいなーと改めて思ったわけです。

その絡みで Catalyst::Plugin::* の話が活発だったりしますが、まぁそれはそれで技術的な討論になってるんで、好ましい事なのかなと。

自分はハッカーでも無くギークでも無く、冷静に考えるでも無く、自分より多くの優秀なプログラマが居るわけです。

ただプログラミングが好きで今までやってきただけで、日々勉強だし、知らない事や新しく覚えた事で何かが開けるたびにやっぱりプログラミングは楽しいなと思ってる訳で、多かれ少なかれプログラマって言う人種はそういう所に喜びを見いだす人種なのかなと思います。

プログラミングして然るべきプロダクトとして世の中に出すにはそれなりの知識と経験が必要ではあるけど、やはり想像した物を考えて組み上げて動かして…、うおっ、期待通り動いたぜーってのは物凄い楽しいんですよね。

で、最近嬉しかったことは前職の時の同僚、しかもプログラマではない人たちが3名ほどプログラミングを覚えたいと言い出して実際にやり始めたって事なんですけど、今の流れからして Perl から始めてもいいんじゃないかなとかやっと言えるようになった感じがする訳です。

そういえば まめこ も最近 Perl 始めましたね。

( 今まではそういう人たちには不本意ながらPHPを勧めてました )*1

このブログは今まで自分のメモ的な側面が強くて、だいぶ読者無視と言うポリシーだったのですが、少しずつ初心者向けと言うか、すぐ写経で分かるとか読んでいって、一つ一つやれば自ずと答えが出てくるとかそういうエントリも増やしていけたらなとか思ってます。

その3人は各々目的あってプログラミングをしようと思い立って三者三様なのですが、いずれにせよプログラミングの世界へ飛び込んできた訳なので、喜んで迎えたいと思うし、出来る限り手助け出来たらいいなと思ってます。

僕ももっと頑張らないとなー。

Moose::Cookbook::Recipe3 - predicate, weak_ref, lazy -

さらに Recipe3 です。

ソース

package BinaryTree;

use Moose;

has 'node' => (is => 'rw', isa => 'Any');

has 'parent' => (
    is => 'rw',
    isa => 'BinaryTree',
    predicate => 'has_parent',
    weak_ref => 1,
);

has 'left' => (
    is => 'rw',
    isa => 'BinaryTree',
    predicate => 'has_left',
    lazy => 1,
    default => sub { BinaryTree->new(parent => $_[0]) },
);

has 'right' => (
    is => 'rw',
    isa => 'BinaryTree',
    predicate => 'has_right',
    lazy => 1,
    default => sub { BinaryTree->new(parent => $_[0]) },
);

before 'right', 'left' => sub {
    my ($self, $tree) = @_;
    $tree->parent($self) if defined $tree;
};

package main;

use Data::Dump qw(dump);
use Perl6::Say;

sub tree {
    my ($node) = @_;
    BinaryTree->new(node => $node);
}

sub slot {
    my ($tree, $node) = @_;

    unless (defined $tree->node) {
        $tree->node($node);
        return 1;
    }

    if ($tree->node > $node) {
        unless ($tree->has_right) {
            $tree->right(tree($node));
            return 1;
        }
        else {
            return slot($tree->right, $node);
        }
    }
    else {
        unless ($tree->has_left) {
            $tree->left(tree($node));
            return 1;
        }
        else {
            return slot($tree->left, $node);
        }
    }
}

my @nodes = 
    map { $_->[0] }
    sort { $a->[1] <=> $b->[1] }
    map { [ $_, rand ] }
    (1 .. 10);

say dump \@nodes;

my $tree = BinaryTree->new;
slot($tree, $_) for (@nodes);

sub bsort {
    my ($tree, $order, $result) = @_;

    if ($order) {
        bsort($tree->right, $order, $result) if ($tree->has_right);
        push(@$result, $tree->node);
        bsort($tree->left, $order, $result) if ($tree->has_left);
    }
    else {
        bsort($tree->left, $order, $result) if ($tree->has_left);
        push(@$result, $tree->node);
        bsort($tree->right, $order, $result) if ($tree->has_right);
    }
}

my $result = [];
bsort($tree, 1, $result);

say dump $result;

解説

二分探索木と二分木ソート

これは 2分木ソート でいいのかな、多分。二分木 - Wikipedia # 二分探索木 を詳しくは参照して下さい。

特定のノードから見て、親と二つの部分木を持つ構造をクラス表現した物。

ノード毎に値が割り振られているとする。あるノードの左の子およびその全ての子孫ノードの持つ値はそのノードの値より小さく、右の子及びその全ての子孫ノードの持つ値はそのノードの値より大きくなるように構成した二分木を二分探索木 (binary search tree) という。 二分木 - Wikipedia # 二分探索木

とあるので、slot 関数ではそのように二分探索木を再帰的に作るような関数になってます。

このようにして出来た二分探索木を、

二分探索木を通りがけ順に探索すると、各ノードの値を大きさ順(あるいは逆順)に得ることができる。 二分木 - Wikipedia # 二分探索木

との事なので bsort 関数では昇順、降順を選んでソート出来るようになってます。

まぁアルゴリズムの解説がメインじゃないのでこんな感じで。

predicate 属性オプション、weak_ref 属性オプション

これはいわゆる初期化済みか否かを指定したメソッド名で取れるようになるってオプションです。

has 'parent' => (
    is => 'rw',
    isa => 'BinaryTree',
    predicate => 'has_parent',
    weak_ref => 1,
);

has_parent メソッドが生えるって事ですね。weak_ref 属性オプションは名前のまま、weak reference を使うって事です。循環参照オブジェクトを作る際は必須ですね。

default 属性オプション、lazy 属性オプション

default 属性オプションは d:id:ZIGOROu:20080606:1212753180 でも出てきましたが、今回は CODEREF になってます。

has 'left' => (
    is => 'rw',
    isa => 'BinaryTree',
    predicate => 'has_left',
    lazy => 1,
    default => sub { BinaryTree->new(parent => $_[0]) },
);

これは逆に言えばそのように書かないと、リファレンスオブジェクトの場合は同じ値が default 値として参照されてしまうので問題になっちゃうので、毎回新たに生成する事を保障する為にそうしているみたいです。

さらに lazy オプションは、

you cannot use the lazy option unless you have set the default option. Moose::Cookbook::Recipe3

との事なので、default 属性オプションが無い場合には lazy 属性オプションは使えません。lazy って言うとタイムキーパーが出来ない id:tomyhero さんを思い出しますね><

で lazy って何かって言えば、実際に値が突っ込まれるまでは初期化を遅らせるって事です。なので再帰的に呼び出す際にも省エネなんだぜって事ですな。

その他

before 'left', 'right' の使い方が素敵ですね。left, right を初期化する際に、さらりと parent を設定してくれる所が小憎らしいですw

SEE ALSO

Moose::Cookbook::Recipe2 - class based constraint, modifier with arguments -

続いて Recipe2 をやっちゃうぞー。

ソース

預貯金に関する英単語が良く分からなかったので調べてコメント振った。

package BankAccount;

use Moose;

# 預金残高
has 'balance' => (isa => 'Int', is => 'rw', default => 0);

# 預金する
sub deposit {
    my ($self, $amount) = @_;
    $self->balance($self->balance + $amount);
}

# 引き落とし
sub withdraw {
    my ($self, $amount) = @_;
    my $current_balance = $self->balance();
    ($current_balance >= $amount) 
        || confess "Account overdrawn"; ### 預金残高より多い額の支払い
    $self->balance($current_balance - $amount);
}

package CheckingAccount;

use Moose;
extends 'BankAccount';

## 借り入れ口座
has 'overdraft_account' => ( isa => 'BankAccount', is => 'rw' );

## 借り入れ口座があって、支払い前にお金が足りない場合は借金して預金残高を必要な分増やす
before 'withdraw' => sub {
    my ($self, $amount) = @_;
    my $overdraft_amount = $amount - $self->balance(); ### 超過した支払い額
    if ($self->overdraft_account && $overdraft_amount > 0) { ### 借り入れ口座があって、借り過ぎの場合
        $self->overdraft_account->withdraw($overdraft_amount); ### 借り入れ口座から支払い
        $self->deposit($overdraft_amount); ### 預金口座に入金
    }
};

package main;

use Data::Dump qw(dump);
use Perl6::Say;
use Test::More qw(no_plan);

my $bank_account = BankAccount->new;

is($bank_account->balance, 0, 'default value');
is($bank_account->deposit(100), 100, 'deposit');
is($bank_account->withdraw(50), 50, 'withdraw');

{
    eval {
        my $check_account = CheckingAccount->new( balance => 1000 );
        $check_account->overdraft_account(2);
    };
    if (my $err = $@) {
        ok($err, 'constraint');
        diag($err);
    }
}

{
    my $check_account = CheckingAccount->new( balance => 1000 );
    ok(!defined $check_account->overdraft_account, 'not set');
    is($check_account->withdraw(250), 750, 'withdraw not exists overdraft_account');
}

{
    my $check_account = CheckingAccount->new( balance => 1000, overdraft_account => BankAccount->new( balance => 2000 ) );
    ok(defined $check_account->overdraft_account, 'set');
    is($check_account->withdraw(1250), 0, 'withdraw exists overdraft_account');
    is($check_account->overdraft_account->balance, 1750, 'overdraft_account balance');
}

解説

制約にはクラス名がそのまま使える

実は d:id:ZIGOROu:20080606:1212748447 に比べて対して真新しい事は無い。

制約にクラスをそのまま使えるよって所くらいか。

## 借り入れ口座
has 'overdraft_account' => ( isa => 'BankAccount', is => 'rw' );

こんな感じ。

後は advice の実践的な使い方とか言いたかった感じかな。

引数ありの modifier

これは大事な点ですね。通常の Perl5 OO で override する場合は、

sub withdraw {
  my ($self, $amount) = @_;
  $self->SUPER::withdraw($amount);
}

のようにしなければなりませんが、before modifier で定義した部分にはそのような箇所はありません、が期待通りに渡されます。

一方で before から元のメソッドに対して新しく引数を増やすと言った事は出来ないようです。

Moose::Cookbook::Recipe1 - has, before, after, extends -

もの凄い乗り遅れた感ですが僕も Moooooooooooooooooose してみる。

とりあえず Cookbook をやってみる事にしてみました。

ソース

まぁ適当にテストとか追加してある。

package Point;

use Moose;

has 'x' => ( isa => 'Int', is => 'ro' );
has 'y' => ( isa => 'Int', is => 'rw' );

sub clear {
    my $self = shift;
    $self->{x} = 0;
    $self->y(0);
}

package Point3D;

use Moose;
extends 'Point';

has 'z' => ( isa => 'Int' );

after 'clear' => sub {
    my $self = shift;
    $self->{z} = 0;
};

package main;

use Data::Dump qw(dump);
use Perl6::Say;
use Test::More qw(no_plan);

my $point = Point->new( x => 10, y => 12 );

ok($point->can('x'), 'has x getter');
ok($point->can('y'), 'has y getter');
is($point->x, 10, 'getter x');
is($point->y, 12, 'getter y');

eval {
    $point->x(1);
};
if (my $err = $@) {
    ok($err);
    diag($err);
}

eval {
    $point->y('foo');
};
if (my $err = $@) {
    ok($err);
    diag($err);
}

$point->clear;

is($point->x, 0, 'after called clear method');
is($point->y, 0, 'after called clear method');

my $point3d = Point3D->new(x => 3, y => 4, z => 5);

ok(!$point3d->can('z'), 'has not z getter');
ok(defined $point3d->{z}, 'has z property');
is($point3d->{z}, 5, 'z property');

$point3d->clear;

is($point3d->x, 0, 'after called clear method');
is($point3d->y, 0, 'after called clear method');
is($point3d->{z}, 0, 'after called clear method');

解説

前提はこんな感じ。

  • use Moose すると strict, warnings が有効になる
  • use Moose した packageMoose::Object がスーパークラスになる
  • has はインスタンス属性を定義する
アクセサの定義
has 'x' => ( isa => 'Int', is => 'ro' );
has 'y' => ( isa => 'Int', is => 'rw' );

は概ね想像の通り、値として 'Int' と言う制約で、ro, rw は想像どおり read only, read write です。

has 'z' => ( isa => 'Int' );

Point3d では is を省略してるんだけど、省略するとアクセサメソッドは生成されない。

メソッドの定義

これは普通の Perl5 OO と同じ形式。

sub clear {
    my $self = shift;
    $self->{x} = 0;
    $self->y(0);
}

但し x は ro で定義してるから値を設定する時はアクセサ経由は出来ないので直で代入してる。

継承

use base じゃなくて、extendsを使う。

extends 'Point';

複数の親を持ちたい場合はリストで指定する。

xtends 'Foo', 'Bar', 'Baz';
modifier

AOP で言う所の advice に当たる物。

after 'clear' => sub {
    my $self = shift;
    $self->{z} = 0;
};

これは clear の実行の後に行う処理って言う意味。

package Person;

use Moose;
use Perl6::Say;

sub hello {
    say 'Hello';
}

package ZIGOROu;

use Moose;
use Perl6::Say;

extends 'Person';

before 'hello' => sub {
    say 'before';
};

after 'hello' => sub {
    say 'after';
};

package main;

ZIGOROu->hello;

まぁこんなんで挙動は分かります。

before
Hello
after

って感じですね。

override したい場合は、override modifier を使う。

コンストラクタ
my $point = Point->new(x => 1, y => 2);   
my $point3d = Point3D->new(x => 1, y => 2, z => 3);

まぁそのままですね。

SEE ALSO

Windows環境でCygwinを使ってPerl実行環境を整える

たまには初心者向けの話でもしてみます。

Windows環境でPerlの実行環境と言えば、真っ先に ActivePerl と言う選択肢がありますが、個人的には*2 Cygwin がお勧めです。

お勧めする理由は、

  • 昔に比べてそれなりに扱いやすくなった。(昔はバッドノウハウだらけだった)
  • Perl に限らず実行環境はそれなりに *nix 的
  • インストールもそんなに難しく無い

とかですかね。VMWare や coLinux で本物の Linux 環境を用意する方が確実ではありますが、他にも豊富な *nix 系のツールが使えるのも強みかなと思います。

インストール

まぁ google:Cygwin インストール とかして頂ければいいんですが、多少古い情報が当たりそうなので、今ググって新しそうなのを。

このページに補足するならば、

Download Without Installing を最初に選択するのをお勧めします

インストール形式ですが、Download Without Installing にして、一度パッケージを手元にダウンロードしてしまうのをお勧めします。

と言うのも Cygwin のパッケージ群はそれなりにファイル数もサイズもあるので、とりあえずダウンロードしてから後で選択してインストールする方が精神的に楽です。

この場合、最初のsetup.exeの起動はダウンロードして来るだけなので、それらが終わったら再度setup.exeを起動して、Install from Local Directoryを選択してインストールする必要があります。

この Download Without Installing の時は パッケージ選択で All を default から install で良いと思います。最初は恐らくどのパッケージが何に使われるとか分からないと思うので。

ただ、Install from Local Directory の時は default で良いと思います。

ツールとか

デフォルトだと DOS Prompt 経由での実行になりますが、Windows の Prompt は何かと苦痛が伴うので何らかのターミナルエミュレータをインストールすべきでしょう。個人的には現時点では TeraTerm をお勧めします。他にも Putty, Poderosa などありますが、現在の TeraTerm は実は進化していて、

  • なんちゃってタブが使える
  • Cygwin 用のランチャーも付属してる
  • もちろん文字コード周りも utf-8 にも対応済み

なので、特に不満な点は無いです。

ここら辺まで揃えたら TeraTerm で cygwin に入れば概ね Linux と似たような Perl 実行環境が得られるはずです。

後はお好みのテキストエディタを用意すればすぐにPerlプログラミングが始められます。

*1:不本意とは Perl を勧められない暗い意味で、PHP が云々と言うより目的を果たすためと言う観点から最短と言う判断の元。予め釘を刺しておくw

*2:と書かないと色々うるさい人が居そうなので強調しておくw