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したいと思っています。