taiyoh's memorandum

@ttaiyoh が、技術ネタで気づいたことを書き溜めておきます。

Perlでのfactory_girlもどきのもの

 ちょっと作ってみた。
 → taiyoh/p5-Test-Factory · GitHub
 PerlだとO/R Mapperが乱立しまくってて何かに依存したモジュールにするのは汎用性が低すぎてないわー、って感じだったので、DBISQL::MakerでDB毎の処理は抽象化させて、SQL的にはinsertだけに絞って機能を提供するようにした。その点ではRubyActiveRecordの影響力が絶大で楽。
 factory_girlほど高機能じゃないけど、作ったパターンをベースに統一されたインターフェイスからデータオブジェクトを生成できる(&DBにそのデータを突っ込む)、ってことについてはとりあえずできる。

package Foo;

use Test::Factory;

my $foo_counter = 0;

factory foo => {
    col1 => 'hoge',
    col2 => 'fuga',
    col3 => 'piyo',
    col4 => sub { ++$foo_counter }
};

package main;

# O/R Mapperではないので、内部でDBI使って自前でinsertする
Foo->dsn("dbi:mysql:dbname;host=localhost", 'db username', 'db password', {});

my $f1 = Foo->build('foo');
print $f1->created; # => 0 (false)
print $f1->col4; # => 1


my $f2 = Foo->build('foo');
print $f2->col4; # => 2

$f1->save; # insert into foo (col1, col2, col3, col4) values (?, ?, ?, ?) /* hoge, fuga, piyo, 1 */

my $f3 = Foo->create('foo');
print $f3->created; # => 1 (true)
print $f3->col4; # => 3

Amon2のコントローラをRailsっぽくしてみる

 ただしルーティングはDispatcher::RouterSimple限定だけど。

これをコントローラクラスでuseしておくと、before_filterとafter_filterが使えるようになる。(ちゃんとテスト書いてないので思いつきレベル)
ただ、Module::Functions::get_public_functionsを使ってる都合で、フィルタとして使う関数はprivate methodとしてprefixに「_」を付けておかないと、public methodと見なされて登録されてしまうのが注意点。

自作のオートマトンのモジュールをCPANに上げた

 承前→ 有限オートマトンの習作として - taiyoh's memorandum
 半年くらい前にこんなエントリ書いていたんですが、気づいたら2月中旬に知らない人がFSM::Simpleって名前でCPANに上げてて、しかも僕の実装より全然シンプルじゃなくて腹が立ったので、"FSM::Tiny"ってモジュール名に変更してアップしました。
 → http://search.cpan.org/~taiyoh/FSM-Tiny/
 ホントはこんなyak shavingじゃなくて、FSM::Tinyを使ったネタがあるんだけど、ブログに書くにはちょっと長いし説明するのがメンドイ。

node-fiberでライフチェンジングとか煽ったことを若干後悔してる

 承前→ node.jsのvmとsynchronizeはライフチェンジング - taiyoh's memorandum
 @hokaccha先生の記事読んで、やっぱインフルエンサーが書くと内容の充実度も反応も全然違うなー、とか思ってたのですが、特にあの記事をぶくましてるけどfiber使ったことない人は、使いどころをちゃんと見極めた方がいい、という注意喚起。もしくは過去の自分の記事に対する懺悔。
 実は以前qiitaにこっそり投稿してたんだけど、以下の処理の時にエラーが発生する。

// sync_test.js
var sync = require('synchronize');

sync.fiber(function() {
  try {
    sync.await((function(defer) {
      console.log("await 1 start");
      setTimeout(function() {
        console.log("await 1 end");
        try {
          sync.await((function(defer2) {
            console.log("await 2 start");
            setTimeout(function() {
              console.log("await 2 end");
              defer2(null, 'ok');
            }, 1000);
          })(sync.defer()));
          console.log("await 2 over");
          defer(null, "ok");
        }
        catch(e) {
          console.log("await 2 error", e);
          defer(e, null);
        }
      }, 1000);
    })(sync.defer()));
  } catch(e) {
    console.log("await 1 error", e);
  }
  console.log("await 1 over");
});

 ずらずら長いが、端的に言えば、sync.await中にもう一回sync.awaitしたらどうなるか、ということを検証したもの。結果は以下のとおりになる。

% node sync_test.js
await 1 start
await 1 end
await 2 error [Error: no current Fiber, defer can'b be used without Fiber!]
await 1 error [Error: no current Fiber, defer can'b be used without Fiber!]
await 1 over

 これだけだとピンとこないかもしれない。じゃあ以下のパターンがあった時は?

  • ちょっと大きめのDBのselect文をsync.awaitで発行してる最中に、別のリクエストでsync.awaitを使ってファイルへの書き込みをする
  • OAuth認証の処理をsync.awaitで発行してる最中に別のリクエストでsync.awaitを使ったselect文を発行する

 他にも色々あると思うけど、ふつーにこういうウェブアプリケーション書いてしまうと、確実にエラーが発生し、「テストでは通ってるけど、本番に上げたらなんかちょくちょくエラーが起きる」という事態が発生する。シングルスレッドでこなすnodeならではの現象だと思う。PerlとかRubyみたいにpreforkしてリクエストを処理したらどうなるかはわからない(そもそもできるのか?このへんは自分はよく知らない)。でも、そういうアプリケーションを書く人は、よっぽどの変態か「自分はJS以外の言語を知らないから」という消極的な理由でアプリケーション実装を選択してるか以外にないだろうと思う。
 要は、プロセス中のシーケンスが単一であればいい。なので、バッチスクリプトを書くときなんかは、すごく効果的なんじゃないかと思う。バッチ実装で非同期とか考えないでいい筈なのにコールバック地獄とか、ただの面倒事でしかないので。

ただのメモ書き、またはArk::ModelsライクなものをAmon2でも用意する

 調子に乗ってAmon2::Modelsとかあるといいかも、って思ってたけど、

package MyApp::Models;

use strict;
use warnings;

use Object::Container '-base';

sub import {
  $_[1] = 'model'; # or something
  goto \&Object::Container::import;
}

register config => sub {
  shift->ensure_class_loaded('MyApp::Config');
  MyApp::Config->current;
};


package MyApp::Config;

use Config::ENV 'PLACK_ENV';

1;

 これで大体やりたいことはできる感じがしてきた。

Emacsのflymakeでcarton+plenvな環境でも無理矢理@INCを通す

 なんか最近Perlづいてます。今日は環境周り(Emacs)。
 久々にちゃんとflymake使おうと思ったので、karupaneruraさんの作成したplenv.elを使って、plenvで構築したperlバイナリでチェックかけるようにしました。ただこれだけだと、cartonでインストールしたモジュールに対してパスが通ってなくて、エラー扱いになるっぽいんですよね。。。
 なので、以下のようなelispを追加しました。再帰的にパスを辿ってファイルを見つける、という処理は
 → Rakefileを再帰的に探してrake testするelisp(TB) - trotrの日記
こちらのエントリにあるRakefileを見つける関数を使わせてもらいました(ありがとうございます><)。

 elisp全然慣れてないとかもあって相当力技ですが、cartonとcpanfileの有無をチェックして、cpanfileとcartonがあるときはcarton execを使って、それ以外は通常どおりguess-plenv-perl-pathで出てきたバイナリを使う、という流れです。絶対どっかで無駄なことしてるだろうな。。。探索し過ぎだし。PERL_CARTON_PATHも使ってるから、carton execする意味が本当にあるのか謎だし。ということで、elisp書ける人はもっとキレイに書いていただきますよう、宜しくお願いします。あと、関数の名前空間汚したくないので、myenv-〜とか適当に入れて書くようにしてますが、この辺って今は何がナウい書き方なんだろ。。。

tokuhirom氏のリファクタリング見てすげーなー、と思った、って話

 typo氏からも「はよ」とか急かされたので、裏ですったもんだした挙句CPAN Authorになってしまいました。
 → http://search.cpan.org/~taiyoh/
 どうぞ宜しくお願いします><

 さて、本題ですが。
 今回Amon2::Web::Dispatcher::RouterSimple::Extendedというモジュールを上げたのですが、エントリ書いた時にgfx氏から「no strict 'refs'の状態でコードをガリガリ書くのはマズい」と指摘を受けました。僕も書いてる時からそれは薄々感じてはいたんですが、有効な策が打ち出せず、お茶を濁すような対応しか出来てなかったわけです。なので、追記の形で泣きついてみたら、翌朝tokuhirom氏からコメントをもらい、「こんなやり方がある」と紹介してもらいました。
 → https://github.com/tokuhirom/p5-Amon2-Web-Dispatcher-RouterSimple-Extended/blob/refactoring/lib/Amon2/Web/Dispatcher/RouterSimple/Extended.pm
 Amon2::Web::Dispatcher::RouterSimple::Extendedでは0.02で上記の氏のリファクタしたものを手動マージで採用し、0.03では更に自分なりに修正したり、バグフィックスを加えています。
 氏のリファクタリングしたソースを見て気づいたことで、一番考えなくちゃいけないのは、「なぜ"no strict 'refs'"をしなきゃいけないか」ってところだなと思いました。見ての通り今回のソースでは、呼び出し元に対して関数を追加しているわけですが、初期の僕の実装でやっていたのは、取り急ぎの追加であって、どのタイミングで関数を追加/変更するかについて、全然考えられてなかったのですね。場当たり的につけたり取り替えたりしてる。しかし氏のソースだと、追加する部分は一箇所にまとめられ、他の場所で関数を付け替えたりしていることは殆どない。submapのインスタンスの有無で、関数内の挙動を変化させている。あと、gotoをうまく使ってて、これはシラフでも全然思いつかなかったです。今後DSLっぽいのを書く時使えそうなので、意識してみたいと思いました。
 あと、0.03ではリファクタリングしてもらったソースに対して2点変更したところがあって、一つは単なるバグ修正です。もう一つはsubmapのインスタンスの持ち方についてで、local使えなくてかっこ悪くなってもいいから、モジュール内の変数でキャッシュさせる方式にして、submapの定義が終わったら明示的に消すようにすればいいや、という風にしました。そうすれば、no strict 'refs'はimportの中だけになるので。
 あとはpod周りだなー。今まで書き捨てみたいな感じでしかやってなかったので、突然cpanに上げることになって相当てんやわんやしておりました。ってか今もしてる。正直読みたくないけど、気づいたらボチボチ直していきます。。。
 それにしても、週末わざわざコメントしてもらったりリファクタリングまでしてもらい、ありがとうございました > tokuhirom氏