MPがありません。

$liiu->mp == 0

Types::TypedCodeRef というモジュールを作りました

概要

Types::TypedCodeRef はPerlで「関数の型」をチェックする型を提供するようなモジュールです。
調べた感じ匿名サブルーチンにいい感じに引数の型とサブルーチンの返り値の型をつけてくれるようなモジュールもなさそうだったので、
AnonSub::Typed という匿名サブルーチンの引数の型と返り値の型をチェックするようなモジュールも作りました。

https://github.com/ybrliiu/p5-Types-TypedCodeRef
https://github.com/ybrliiu/p5-AnonSub-Typed

使い方

use v5.30;
use Test2::V0;
use Types::TypedCodeRef qw( TypedCodeRef );
use Types::Standard qw( Int Str );
use AnonSub::Typed qw( anon );

my $type = TypedCodeRef[ [Int, Int] => Int ];
ok $type->check(anon [Int, Int] => Int, sub { $_[0] + $_[1] });
ok !$type->check(0);
ok !$type->check([]);
ok !$type->check(sub {});

package Hoge {

    use Moo;
    use Types::Standard qw( ArrayRef Int );
    use Types::TypedCodeRef qw( TypedCodeRef );

    has event_handlers => (
        is       => 'ro',
        isa      => ArrayRef[ TypedCodeRef[ [ Int, Int ] => Int ] ],
        required => 1,
    );

}

my $hoge = Hoge->new(event_handlers => [ anon [Int, Int] => Int, sub { $_[0] + $_[1] } ]);
is $hoge->event_handlers->[0]->(12, 13), 25;

done_testing;

モチベーション

Perlでコード書いているときにもObserverパターン的なアーキテクチャを作りたくなることがあるのですが、
いちいち愚直にObserverパターンを実装するのは面倒くさいですし、かといってイベントハンドラみたいにCodeRefだけわたして雑に実装しようとすると、
イベントハンドラに登録するコールバック関数に渡す引数と関数の返り値の型がわかりにくくなり、コードの可読性が落ちるので、なんとかしたくなり実装しました。

最初は Function::Parameters や Function::Return あたりのモジュールで匿名サブルーチンの引数と返り値の型付けもできるかなーと思っていましたが、どうやら匿名サブルーチンの場合はattributeが有効にならないようだったので、(attributeの実装的にも恐らくそうなる)、匿名サブルーチンの引数と返り値の型チェックを行うモジュール AnonSub::Typed の実装を行ってから、Types::TypedCodeRef を実装しました。

実装について

AnonSub::Typed

型に関する情報もGCで一緒に管理されてほしいので、匿名サブルーチン本体をblessしたinside-outオブジェクトを返すクラスとして実装していて、それにパラメータの型や返り値の型情報を紐付けています。
クラスビルダーには Class::InsideOut というクラスビルダーを利用しています。
後は愚直に anon という AnonSub::Typedインスタンスを作る関数を提供しています。

Types::TypedCodeRef

Type::Tiny のコードを参考にしつつ、総称型のような型を作っています。
同じインターフェースの関数かどうかは最近kfly8さんが作られた Sub::Meta を利用して比較しています。
また、外部からコールバック関数を登録すれば AnonSub::Typed 以外のインスタンスでも比較できるような設計にしています。

今後の展望

AnonSub::Typed はもうやることはないかなーという感じですが、
Types::TypedCodeRef の方は AnonSub::Typed 以外を利用しているパターンでも関数の型を取得できる場合があればコールバック関数を登録しなくても比較できるようにしたいです。
後はテストとドキュメントを充実させたらCPANizeしたいと思っています。