Masteries

技術的なことや仕事に関することを書いていきます.

最近の読書法

この記事は, 「はてなエンジニア Advent Calendar 2019」の21日目の記事です.

qiita.com

昨日は id:dekokun さんの「Kinesis Data Streams + Lambdaが詰まった時の対処法」でした.

dekotech.dekokun.info

最近の読書法

「読書習慣が定着しないな...」という課題を延々と持ち続けていて, 積読本も無限に溜まっていたのですが, 最近徐々に消化が進んできました. うまくサイクルが回り始めた理由を考えていて, 幾つか気付いたことを書き残しておこうと思います. 結論としては, 本を読むにあたって現在は以下の3点に気をつけています:

  • 少しでもいいので読む
  • 理解できなくてもひとまず読む
  • とはいえ, グッと来なかったら読み飛ばす

少しでもいいので読む

既に読書の習慣が定着しているならともかく, そうでないのなら, まずは「定期的に本を読む」ようになるのが大事だと思いました.

読書の習慣がなかった頃は, 面白そうな本をとりあえず買うものの, 読書の習慣が定着していないので当然のように積まれていって, だからこそ気持ちが高まってきた時に「消化するぞ! たくさん読むぞ!!!」と思うものの, その気持が逆に読書のハードルを自分で高めてしまい(折角読むのだからたくさん読もう, 折角読むのだから理解しよう, 等...), 結果として挫折する... といった悪循環を, 延々と繰り返してしまっていました.

この辺りは, 今年「小さな習慣」を読んだ影響があって, 毎日少しずつでもいいので, なるべくコンスタントに読書をしていく... という習慣を試みていて, それが徐々に定着してきているように感じます. とにかく, 本を読むハードルを意図的に, どんどん下げていっています.

papix.hatenablog.com

直近だと,

  • Kindleで, 1日2ページは読むようにする
  • 飲みすぎたり疲れた日は休んでも良い, が翌日は2倍読む(2倍と言っても4ページ)

...という感じでやっています. Kindleで2ページ程度であれば, どう転んでも10分もあれば読み切れるので, 出勤時間にサクッと読んでしまえますし, 仕事で多少疲れた時でも寝る前にベッドの中でなんとか読了できる量だと思います(そして, 本当に疲れている時は前述のように諦めてもよく, 代わりに翌日2倍読む).

これの良い所は, 「とりあえず2ページで...」という気持ちで本を読み始めると, 時と場合によってはだんだん面白くなってきて, 結果として2ページ以上読み進めている... ということが結構ある点です. 読書に対するハードルを, 極限まで下げているはずなのに, 結果としては気合いを入れて本を呼んでいた時よりもたくさんの分量を, コンスタントに読み進めることができるようになったと思います.

理解できなくてもひとまず読み進める

読書の習慣が定着しない頃, 何かのきっかけで本を読むか! となった時, 先にも述べたように, 「せっかく読むのだから, 全部理解しよう...!」と意気込みすぎた事が多かったように思います. 結果としてページは進まず, 「うーん, うーん...」と悩んでいるうちに時間が過ぎ去ってしまい, 更にそのせいか「また続きを読もう!」という気持ちになれない... という, 悪循環を起こしていました.

これもまた, 読書のハードルを下げる観点の1つではあるのですが, まずはとりあえず「どんな事が書かれているかわかればいいや」という気持ちで本と向き合うようにしています. サクサク読んでいって, 興味深いと思ったところだけしっかり読む, という雰囲気です. 気持ちとしては, 「じっくり読もうとして進まないより, とりあえず1冊読み切った方がまだ有益」と思うことにしています.

...とはいえ, そういう読み方をしていても, 1冊あたり(分量によりますが)どれだけ少なくても2〜3ポイントくらいは学びというか, 新しい視点が得られるものだなあと思っています. また, 何より「この本には, こういう事が書いてある」というインデックスを作ることが出来るのが有益で, まずはそこを目指す程度で十分ではないか, と思うようにしています. そうすれば, ある意味で本を「外部記憶」にできる訳で, いつか将来, 「これ, あの本に書いてあったな...!」というタイミングがあれば, また本を開いて, 今度は必要なところを理解出来るまで読み込む... という使い方が出来るはずです.

課題としては, 本のジャンルによってはこういう読み方が明らかに不向きなことがある(例えばプログラミング言語の解説/入門書などはそういうジャンルと思います)というところで, そこについては今後の課題としたいと思います.

とはいえ, グッと来なかったら読み飛ばす

先に, 「理解できなくてもひとまず読み進める」と書きましたが, とはいえ読んでいて「グッと来ない」と思った所は, 思い切って読み飛ばすようにしています. 「せっかく読書するのだし, 全部読むようにしよう!」と思っていた時期もあったのですが, そのような理由で無理やり文言と向き合っても頭に入ってきませんし, そういう状態で本を読んでも得るものはほとんどない... という結論に至りました.

なので, 最近は「この辺りはあまりグッと来ないな...」と思ったら, 各節のタイトル等をサッと眺めて読み飛ばすようにしています. 「各節のタイトルをさっと眺めておく」というのは大事だと思っていて, 先に「本のインデックスを作って外部記憶にする」という話をしましたが, そのためにもそれくらいは読んでおく必要があると思っています.

まとめ

とにかく, まず本を読む習慣を定着させたい! と思うのであれば, ただひたすらに「本を読むハードルを下げる」必要があると思っています. ここで紹介した3つのポイントは, それを実現するために自分が実施していることです.

かつての自分は, 「本をたくさん読み, そして多くのことを理解したい」と思っていました. しかし, 二兎を追う者は一兎をも得ず, という諺があるように, (少なくとも自分には)その2つをシュッと両立することはできませんでした...

なので, 最近はこうした施策によって本を読むハードルを下げ, 読書習慣を定着させ, その次のフェイズとして, 読解力や本から得るものを増やしていく... という取り組む方が良さそうという結論に至っています. 皆さんはどのようにして「本」と向き合っていますか? この機会に言語化してみると得るものが多いかもしれません.

明日の担当は id:side_tana さんです. 宜しくおねがいします.

チームでPerlを書く時に考えていること

id:papix です. この記事は, Perl Advent Calendar 2019の16日目の記事です. 昨日は bayashi_net さんの「今年書いた何か Github Actions」でした.

bayashi.net

さて, 今日は前回2日目に書いたキーワードプラグインについて, 実際の実装方法について解説する... と予告していましたが, 師走っているのと, XSについて記憶がかなり失われていることなどから, 準備に時間が足りませんでした. すいません...

papix.hatenablog.com

代わりに, 「チームでPerlを書く時に考えていること」というタイトルで, 自分自身が業務の中で, チームメンバーと一緒にPerlのプロダクトを開発していく中で, 気をつけていること, 考えていることについて綴ろうと思います.

注意

  • これはあくまで id:papix 個人の意見, 見解, 感想です
  • チームでPerlを書くにあたって, 必ず下記の点について気をつけないといけない, 守らないといけない! という訳ではありません

チームでPerlを書く時に考えていること

変数は宣言だけしない

変数を宣言するときに, 条件によって初期値を切り替えたい, と思うことがあると思います. そういう時の書き方として...

my $var;
if ($cond) {
    $var = 1;
} else {
    $var = 2;
}

このように書くことも出来ます. ただ最近, 上記のコードのように, 初期値の代入がない, ただの変数の宣言を見るとドキッとするようになりました. 理由はなんだろう? と考えると, 宣言した変数が一瞬でもundefである状態が怖いのかな? と思っています. その直後で正しく初期化しなければ, 意図しない挙動になってしまいそう... と思っているのかもしれません.

例えば, チームでプロダクトを開発していて, 様々な事情があって, my $var;という変数の宣言と, その変数の初期化の処理の間に, 更に別の処理が入っていって... と複雑化していくことはない... とは言い切れないと思います.

my $var;

... 他のいろいろなコード ...

# ようやくここで $var を初期化する
if ($cond) {
    $var = 1;
} else {
    $var = 2;
}

こういう時に, もし間違って ... 他のいろいろなコード ... の中で $var を使ってしまうと, 当然初期化の処理が実行されていないので意図しない挙動になります. 一方で, $var は変数として宣言されているので構文としては正しく, 結果としては「なぜundefなんだろう...?」と悩むことになります.

...というわけで, 最近は変数の宣言と初期値の代入は必ず同時にするように心がけています. 例えば, 先の例であれば次のように書きます:

my $var = $cond ? 1 : 0;

条件が複雑な場合は, 次のようにdoを使ったりもします. doの場合, スコープが区切られるので初期値を生成するために一時的に変数を宣言する, といったこともできます.

my $var = do {
   my $cond = ...;
   if ($cond == 1) {
      'a';
   } elsif ($cond == 2 ) {
      'b';
   } else {
      'c';
   }
};

あるいは, 初期値を生成するためのメソッドを用意するのも良いでしょう:

my $var = _prepare_var(...);

importするメソッドは明記するようにする

Perlには, import/exportという仕組みがあります. 例えば, 次のようなSampleパッケージがあるとしましょう. このコードでは, Exporterモジュールを使って, func_a, func_bをエクスポートしています.

package Sample;

use Exporter 'import';
our @EXPORT = qw(func_a func_b);

sub func_a { ... }
sub func_b { ... }

1;

こうすることで, our @EXPORT に含まれる名前のメソッドは, そのパッケージをインポートするだけで自動的に利用出来るようになります.

use Sample;

func_a(); # => Sample#func_a が呼べる
Sample::func_a(); # import/exportの仕組みを使わないなら, こう書かないといけない

func_b(); # => Sample#func_b も呼べる

import/exportの仕組みはとても便利です. ただ, チームでPerlを開発していて, 次のようなコードに手を加えることになったら... どう思いますか?

use Sample1;
use Sample2;
use Sample3;
use Sample4;
use Sample5;

func_a();

「...func_aってどこでexportされているんだ?」と困惑してしまいそうです. そこで, チームでPerlを書くような場合は, importするメソッドを次のように明記するようにしています.

use Sample1 qw(func_a);
use Sample2;
use Sample3;
use Sample4;
use Sample5;

func_a();

このコードのように, useでパッケージをimportする際, 文字列で関数名を渡すと, その名前の関数だけをインポートすることができます. こうすれば, 「なるほど, func_aSample1にあるんだな!」と一目瞭然ですね.

use Sample1 qw();

func_a(); # => 呼べない
Sample1::func_a(); # => 呼べる

ちなみに, 上記のコードのように, use Sample1 qw(); としてモジュールを呼び出すと, 全ての関数をインポートしないように出来ます. この場合, パッケージ名を付与して関数を呼び出してあげることになります

さいごに

チームでPerlを書く時に気をつけていることをご紹介しました. Perlは超手軽につかえて便利なプログラミング言語ですが, とはいえチーム開発というシーンにおいてはその手軽さに甘えた時に落とし穴にハマる, みたいなこともあるので, 意識するべきところを意識して書いていきたいものです. ところでこのエントリは時間の制約上かなりシュッと書いたので, もっといろいろ気をつけポイントはあると思うので, 「○○○はどうですか?」とか, 「✕✕✕も罠になるので気をつけると良さそう」といった感想をお待ちしています.

Perl Advent Calendar 2019, 明日の担当は, doikojiさんです.

VSCodeで開かれたシェルであることを環境変数から知る方法

結論から述べると,

qiita.com

こちらのQiitaエントリの, 統合ターミナルかその他ターミナルかを判別するの項を読みましょう.


「VSCodeで開いたシェルだけ○○をする」といったことを実現しようと思った時, Googleなどで検索すると VSCODE_PID 環境変数で識別するとよい... という情報を得ることができたのですが, VSCode 1.26より VSCODE_PID 環境変数はセットされなくなったので, 上記エントリにもあるように, TERM_PROGRAM 環境変数で識別するようにしましょう.

# VSCodeで開かれたシェル
$ echo $TERM_PROGRAM
vscode

# 例: iTerm2で開かれたシェル
$ echo $TERM_PROGRAM
iTerm.app

...といった形で, どのプログラムでシェルを開いたか? という情報が TERM_PROGRAM 環境変数に入っているようです. これを知るまで数時間潰してしまったので, 備忘録のために書き記しておきます.

Syntax::Keyword::TryとPerlのキーワードプラグイン (その1)

id:papix です. この記事は, Perl Advent Calendar 2019の2日目の記事です. 昨日は, id:karupanerura さんの「2019年の最先端のPerl開発ボイラープレート」でした.

qiita.com

今日は, 昨日のエントリでも触れられていたSyntax::Keyword::Tryについて, さっくりとアレコレ綴りたいと思います.

Perlと例外処理

Perlで例外処理をするにあたって, 例えばJavaのようなtrycatchといった仕組みは言語仕様として提供されていません. そのため, Perl Monger達はtry/catchのような機能を提供するモジュールを用意して対処してきました. その中でも著名なものの1つは, Try::Tinyでしょう.

metacpan.org

Try::Tinyを使えば, 例外(die)が起こるコードについて, 次のように書くことができます.

use Try::Tiny;

sub run {
    try {
        die "!!!";
    } catch {
        warn "recovery";
    };
}

run();

tryで囲まれたコードで例外が発生した場合, Try::Tinyはそれをよしなにcatchしてくれて, catchで囲まれたコードを実行してくれる訳です.

Try::Tinyの罠

さて, Try::Tinyには, 何だかんだで誰もが1度はハマる罠が存在します. それが次のコードです.

use Try::Tiny;

sub run {
    try {
        # ... 例外が起こるかもしれない処理 ...
    } catch {
        warn "recovery";
        return 1;
    };

    # ... 何かしらの処理 ...

    return 2;
}

my $result = run();
print "$result\n";

このコードを実行したとき, print "$result\n"; は何と表示されるでしょうか? コードを書いた人の意図としては,

  • tryで囲まれたコードで例外が発生すれば, catch で囲まれたコードが実行され, 結果としてrunは1を返す
  • tryで囲まれたコードで例外が発生しなければ, # ... 何かしらの処理 ... の部分にある何かしらのロジックが実行され, 結果としてrunは2を返す

...と考えていそうです. しかしながら, 例えば # ... 例外が起こるかもしれない処理 ... を, 実際に例外が起こるようなコード(例えばdie("!!!"))にしたとしても, このコードの実行結果(= print "$result\n";の出力)は, 2になります.

これは何故でしょうか? 結論から言えば, Try::TinyはPerlの既存の仕組み(プロトタイプなど)を利用して, 強引にtry/catch風の構文を再現しているから... ということになります. 実は, 先のコードは下記のコードと等価なのです.

    try (
        sub {
            # ... 例外が起こるかもしれない処理 ...
        },
        catch (
            sub {
                warn "recovery";
                return 1;
            },
        ),
    );

そのため, return 1はTry::Tinyが提供するcatchという関数の第一引数に渡るサブルーチンリファレンスの返り値となり, 最終的にこの値はTry::Tinyが提供するtryという関数の返り値となるわけです.

また, Try::Tinyの場合, 最後のコードブロックに;をつける必要があるというのも, よくある罠と言えるでしょう.

use Try::Tiny;

sub run {
    try {
        # ... 例外が起こるかもしれない処理 ...
    } catch {
        warn "recovery";
        return 1;
    } # ← ここに ; が必要!!!

    # ... 何かしらの処理 ...

    return 2;
}

Syntax::Keyword::Try

さて, そういったTry::Tinyの問題を解決する, Syntax::Keyword::Tryというモジュールがあります.

metacpan.org

このモジュールを使うと, Perlにおける例外処理を次のように実装することができます:

use Syntax::Keyword::Try;

sub run {
    try {
        # ... 例外が起こるかもしれない処理 ...
    } catch {
        warn "recovery";
        return 1;
    }

    # ... 何かしらの処理 ...

    return 2;
}

my $result = run();
print "$result\n";

末尾の;が不要だったり, # ... 例外が起こるかもしれない処理 ...で例外が起おきた時も, 関数runは当初意図していた通り, 1を返します(当然, 例外が発生しなければ return 2 によって2を返す). Syntax::Keyword::Tryには, Try::Tinyに潜む罠が存在しないのです.

「キーワードプラグイン」

さて, このモジュールは, Perlの"キーワードプラグイン"という仕組みを使って実装されています. キーワードプラグインはPerl 5.12(2010年リリース)から実装された機能で, Perlのパーサーにフックを設定し, 構文を拡張できるようになっています.

Syntax::Keyword::Try以外にも, キーワードプラグインを利用したモジュールは公開されていて, 例えばFunction::Parameters(関数の定義をより良い形で記述できる), Keyword::Boolean(true/falseを提供する), Switch::Plain(switch構文を提供する)などがあります.

metacpan.org

metacpan.org

metacpan.org

id:charsbar さんが2016年に参加した, London Perl Workshop 2016で, Syntax::Keyword::Tryを開発した, Paul Evans氏の発表があったそうですが,

Perlをもっとよくしていくには、公開されている機能をギリギリまで使い倒して、できる人たちに「そんな無茶なやり方よりもっといい方法がある」と言わせるといい

charsbar.hatenadiary.org

...と仰っていたそうです. キーワードプラグインによって今のPerlを拡張し, 日々の開発に役立て, それを通して未来のPerlに貢献する... というのは, とてもおもしろそうですね.

次回予告

...というわけで, キーワードプラグインの実装までご紹介出来ればよかったのですが, ちょっと準備に時間が足りず... 今回はここまでとさせてください. 改めて, Perl Advent Calendar 2019の16日目にて, 「キーワードプラグインを使ったモジュールの実装について」をご紹介させてもらえればと思います!!!!!!

Perl Advent Calendar 2019, 明日の担当は, take_3さんです.

詳解 Class::Accessor::Typed

Kichijoji.pm20で発表した, 「詳解 Class::Accessor::Typed」の資料を公開します. 今回は実験的にScrapboxに書いてプレゼンテーションモードで発表したので, それを加筆修正してはてなブログで公開することとしました.

Perlの「型」について

  • そもそも型とは?
    • Wikipedia曰く...
    • コンピュータで扱うデータの決められた形式・種類(整数型、浮動小数点型、文字型、集合型、ポインタ型 等)→データ型
  • Perlは動的型付け言語
    • GoやScalaのような静的な型はない
    • perldocのperldataによると...
      • Perl には、スカラ、スカラの配列、「ハッシュ」とも呼ばれるスカラの 連想配列という 3 つの組み込みデータ型があります。
    • ナルホド...

型制約

  • 例えば...
sub hello {
    my $name = shift;
    printf "Hello, %s!!!", $name;
}

hello('papix'); # => Hello, papix!!!
hello(+{}); # => Hello, HASH(0x7fdabc8035e0)!!!
  • Perlのスカラ変数は(当然)様々な種類のデータを持てる
    • 文字列のようなもの, 数字のようなもの, 配列やハッシュのリファレンス...
    • 例えば, 文字列を期待する所にリファレンスが来ると困る
      • エラーになる, 警告が出る...
      • 何れにせよ, 期待通りの結果にならない!!
        • 「期待通りの結果にならない」のに, 正しく動作するように見えるとかなり辛い
        • 例えば, 上のサンプルコードはHASH(0x7fdabc8035e0)という意図しない出力をするが, エラーにはならず処理はそのまま実行され続ける(警告は出る)
  • 静的型付け言語であれば, コンパイル時にエラーとすることができる
    • しかし, 動的型付け言語は実行時に型が動的に定まるので, コンパイル時にエラーにすることができない...

ソリューション

  • モジュールで動的に型を定義するアプローチ
    • 前述のように, Perlは動的型付け言語 = 実行するまで何が来るかわからない
      • モジュールで型を定義し, 実行時にそれを使って検証する
        • 型が適切でない場合, エラーになる
        • しかし, エラーにならず, そのまま素通りしてしまうよりは遥かにマシ
    • 例: Smart::Args
      • hello(+{}); は, Smart::Argsによってエラーとなる(その時点でdieする)
use Smart::Args qw(args_pos);

sub hello {
    args_pos my $name => 'Str';
    printf "Hello, %s!!!", $name;
}

hello('papix'); # => Hello, papix!!!
hello(+{}); # => '$name': Validation failed for 'Str' with value HASH(0x7f8f298035e0)
  • 型の定義
    • 例: Mouse::Util::TypeConstraints
      • クラスビルダーのMouseに付属している, 基本的な型 + 型の拡張を実現するモジュール
        • Smart::Argsも内部ではこれを使っている
        • Type::Tinyというモジュールを使ったSmart::Args::Type::Tinyというモジュールもある
      • 次のような型が用意されている:
 Any
  Item
      Bool
      Maybe[]
      Undef
      Defined
          Value
              Str
                  Num
                      Int
                  ClassName
                  RoleName
          Ref
              ScalarRef
              ArrayRef[]
              HashRef[]
              CodeRef
              RegexpRef
              GlobRef
                  FileHandle
              Object

Perlのクラスビルダーについて

クラスビルダーとは...

  • Perlには, もともとオブジェクト指向プログラミングという概念はなかった
    • 後に, blessなどの仕組みを作って, 「後付で」実装された
package MyPackage;

sub new {
    my ($class, $name) = @_;
    return bless { # blessを使って, hash referenceをMyPackageで"祝福"する
        name => $name,
    }, $class;
}

sub name {
    my $self = shift;
    return $self->{name};
}
use MyPackage;

my $obj = MyPackage->new(name => 'papix');
print $obj->name; # => papix
  • このあたりの記述を, より便利に/シンプルに記述できるようにするのがPerlにおける「クラスビルダー」
  • Perl界では, さっくり大別して, 「フルスペック」なクラスビルダーと「シンプル」なクラスビルダーがある
    • フルスペック
      • getter/setter(アクセサ)などが自動的に提供される
      • 型によるバリデーションなどがある
      • Mixinなどを実現するための仕組みがある
    • シンプル
      • getter/setter(アクセサ)の提供をするくらい

フルスペック

  • Mouse
    • XSを活用しまくっていて軽量
      • 国内ではデファクトスタンダード(と思う)
    • 注: XSというのは, Perlの処理をC言語で実装するための仕組みです
      • C言語で実装されているのでPerlに比べて高速
  • Moose / Moo
    • 最近はMooを使うよう推奨されている
    • Moose / Mooをよしなに使い分ける, Any::Mooseというモジュールもあった
  • Mo, Mとかもある... (ネタモジュールの域)

シンプル

  • Class::Accessor / Class::Accessor::Fast
  • Class::Accessor::Lite
    • Class::Accessorを更にシンプルにした実装
  • Class::Accessor::Lite::Lazy
    • Class::Accessor::Liteに遅延ロードを提供するモジュール

Class::Accessor::Lite

  • newが真ならコンストラクタを自動生成する
  • rw/ro/woで, それぞれread/write, read only, write onlyのアクセサを生成する
package MyPackage;

use Class::Accessor::Lite (
    new => 1,
    rw  => [ qw(foo) ],
    ro  => [ qw(bar) ],
);
use MyPackage;

my $obj = MyPackage->new(
    foo => 'a',
    bar => 'b',
);

print $obj->foo; # => a
$obj->foo(123);
print $obj->foo; # => 123

print $obj->bar; # => b
$obj->bar(123); # 'main' cannot access the value of 'bar' on objects of class 'MyPackage'

Class::Accessor::Lite::Lazy

  • Class::Accessor::Liteに加えて, rw_lazy, ro_lazyを提供
    • コンストラクタに値を渡さなかった時, デフォルト値を遅延して生成できる(アクセサを読みに行った時に生成する)
  • 例えば次のコードは, fooを読み込んだ時に_build_fooの返り値が自動的に初期値として使われる
package MyPackage;

use Class::Accessor::Lite::Lazy (
    rw_lazy => {
        foo => '_build_foo',
    }
);
sub _build_foo {
    my $self = shift;
    ...
}

課題感

  • フルスペックのクラスビルダーは重厚長大
    • コードも複雑
    • しかし型の恩恵を得られるのは利点
  • シンプルなクラスビルダーはコードも単純
    • しかし型の恩恵はない

Class::Accessor::Typedの紹介

  • シンプルなクラスビルダー(Class::Accessor::Lite)を軸に, 型の恩恵を受けられるようにした
    • Smart::Args like (Mouse::Util::TypeConstraintsベース)
    • Class::Accessor::Lite / Class::Accessor::Lite::Lazy + Smart::Args, みたいな雰囲気
      • はてなではよく使われる組み合わせ
  • シンプルなクラスビルダーのようにコードは(フルスペックのクラスビルダーと比べると)かなり単純で, しかし型の恩恵を受けることができる

使い方

Class::Accessor::LiteやClass::Accessor::Lite::Lazyを使ったことがある方であれば, 使い方がかなり似ているということがわかるのではないかと思います.

package MyPackage;

use Class::Accessor::Typed (
    rw => {
        baz => { isa => 'Str', default => 'string' },
    },
    ro => {
        foo => 'Str',
        bar => 'Int',
    },
    wo => {
        hoge => 'Int',
    },
    rw_lazy => {
        foo_lazy => 'Str',
    }
    ro_lazy => {
        bar_lazy => { isa => 'Int', builder => 'bar_lazy_builder' },
    }
    new => 1,
);

sub _build_foo_lazy  { 'string' }
sub bar_lazy_builder { 'string' }

以下, Class::Accessor::Typedでできる事をコードを例示しつつ説明します.

型のチェック

コンストラクタに渡した値が指定した型を満たさない場合, エラーになります.

{
    package MyPackage;
    use Class::Accessor::Typed (
        rw => {
            foo => 'Int',
        },
        new => 1,
    );
}

my $obj = MyPackage->new( foo => 'string' ); # => 'foo': Validation failed for 'Int' with value string

もちろん, setterで値を上書きした時も型によるチェックが行われます.

{
    package MyPackage;
    use Class::Accessor::Typed (
        rw => {
            foo => 'Int',
        },
        new => 1,
    );
}

my $obj = MyPackage->new( foo => 123 );
$obj->foo('string'); # => 'foo': Validation failed for 'Int' with value string

コンストラクタ

コンストラクタの引数が足りない場合, エラーになります.

{
    package MyPackage;
    use Class::Accessor::Typed (
        rw => {
            foo => 'Int',
        },
        new => 1,
    );
}

my $obj = MyPackage->new(); # => missing mandatory parameter named '$foo'

ちなみに存在しないパラメータを渡した時は警告が出ます.

{
    package MyPackage;
    use Class::Accessor::Typed (
        rw => {
            foo => 'Int',
        },
        new => 1,
    );
}

my $obj = MyPackage->new(foo => 1, bar => 1); # unknown arguments: bar

コンストラクタの引数が足りない時, もしdefaultオプションが指定されていれば, それを利用します.

{
    package MyPackage;
    use Class::Accessor::Typed (
        rw => {
            foo => { isa => 'Int', default => 123 },
        },
        new => 1,
    );
}

my $obj = MyPackage->new();
print $obj->foo; # => 123

また, rw_lazy, ro_lazy を利用すれば, アクセサから読み込んだ時に値を生成できます.

{
    package MyPackage;
    use Class::Accessor::Typed (
        rw_lazy => {
            foo => 'Int',
        },
        new => 1,
    );

    sub _build_foo { 123 }
}

my $obj = MyPackage->new();
print $obj->foo; # => 123

ベンチマーク

                Rate Moose Moo(ISA) C::A::Typed  Moo C::A::Fast Mouse C::A::Lite
Moose        25830/s    --     -89%        -92% -96%       -96%  -97%       -97%
Moo(ISA)    240604/s  831%       --        -27% -60%       -62%  -70%       -76%
C::A::Typed 328975/s 1174%      37%          -- -45%       -49%  -59%       -67%
Moo         595781/s 2207%     148%         81%   --        -7%  -25%       -39%
C::A::Fast  641222/s 2382%     167%         95%   8%         --  -20%       -35%
Mouse       799455/s 2995%     232%        143%  34%        25%    --       -19%
C::A::Lite  983039/s 3706%     309%        199%  65%        53%   23%         --
  • Mooseや, 型のチェックをするMoo(Moo(ISA))に比べると, Class::Accessor::Typedは高速
  • 流石に, Class::Accessor::Fast, Class::Accessor::Liteよりは遅い
  • 驚異的なのはMouseで, なんとフルスタックなクラスビルダーなのにClass::Accessor::Typedより普通に高速(!?)
    • 流石XS...!!!

展望

  • Anikiのinflateのような仕組みを入れたい
    • 初期値を渡した時, 指定したサブルーチンを通して格納する
    • 例えば, 日付の文字列を渡すと, サブルーチンを通してDateTimeなどに自動的に変換するとか...
  • 以下のコードではfilterというオプションにしているけれど, どういう名前が良いか...?
    • ご意見募集中です...
package MyPackage;
use Class::Accessor::Typed (
    rw => {
        name => { isa => 'Str', filter => sub { $_[0] . "!!!" } },
    },
);
use MyPackage;

my $obj = MyPackage->new(name => 'papix');
print $obj->name; # => papix!!!

※Kichijoji.pm20終了後, ひとまず実装してみました:

github.com

まとめ

Class::Accessor::Typedを作ってみました. フルスタックではないけれど, そこそこ可読性が高く便利で, かゆい所に手が届くモジュールになったのではないかと思っています. 興味がある方は是非使ってみて下さい. また, ご意見ご感想やPull Requestも常時募集中です.