Slide 1

Slide 1 text

黒魔術で独自定義のenum 型制約を満たす値のリ ストを取得する話 Gotanda.pm #18

Slide 2

Slide 2 text

自己紹介 liiu / twitter @_ybrliiu Perl を書くお仕事をしています 趣味 Perl 歴史 最近廃墟めぐりやVTuber にハマっています

Slide 3

Slide 3 text

Perl5.30 release!!!

Slide 4

Slide 4 text

Perl でも大規模なプロダクトになると型制約をつけて開発することが多いと思います CPAN にはType::Tiny, Moose, Mouse など、型制約を実現するモジュールがあります お仕事ではMouse を使っているので、独自型を定義するときは Mouse::Util::TypeConstraint を利 用しています use Mouse::Util::TypeConstraints qw( enum ); use Mouse::Util::TypeConstraints qw( enum ); enum RGBColors => qw( red green blue ); enum RGBColors => qw( red green blue );

Slide 5

Slide 5 text

enum 型の型制約を満たす値のリストを取得したい状況が発生しました。 諸事情で型定義されているところをいじるのは難しい どうすれば可能なのか?

Slide 6

Slide 6 text

型情報を取得する Mouse::Util::TypeConstraints では、 find_type_constraint という関数で型情報 (Mouse::Meta::TypeConstraint のインスタンス) を取得できます しかし、このTypeConstraint オブジェクトから得られる情報は、型名と型制約を満たすか満たさ ないかを判定するCodeRef くらいです use v5.28; use v5.28; use warnings; use warnings; use utf8; use utf8; use Mouse::Util::TypeConstraints qw( enum find_type_constraint ); use Mouse::Util::TypeConstraints qw( enum find_type_constraint ); use DDP +{ deparse => 1, use_prototypes => 0 }; use DDP +{ deparse => 1, use_prototypes => 0 }; enum RGBColors => qw( red green blue ); enum RGBColors => qw( red green blue ); my $type_constraint = find_type_constraint('RGBColors'); my $type_constraint = find_type_constraint('RGBColors'); say $type_constraint->name; say $type_constraint->name; say 'Pass' if $type_constraint->check('red'); say 'Pass' if $type_constraint->check('red'); # check メソッドで渡された引数が、型制約を満たすかをチェックする関数が入っているインスタンス変数 # check メソッドで渡された引数が、型制約を満たすかをチェックする関数が入っているインスタンス変数 # アクセサを通して取得していないのですでにヤバそうな雰囲気がある # アクセサを通して取得していないのですでにヤバそうな雰囲気がある p ¥$type_constraint->{compiled_type_constraint}; p ¥$type_constraint->{compiled_type_constraint};

Slide 7

Slide 7 text

enum 型の型制約を満たす値のリストの情報はどこにもない・・・ 詰みでは・・・

Slide 8

Slide 8 text

そう言えばレキシカル変数を無理やりのぞくことができる変態モジュールがあるとどこかで見た 記憶が それを使って型制約を満たすかをチェックするクロージャを作っているメソッドのレキシカル変 数をのぞけば、型制約を満たす値のリストを取得可能では・・・?

Slide 9

Slide 9 text

PadWalker https://metacpan.org/pod/PadWalker レキシカル変数をレキシカルスコープの外から見たり書き換えたりできます。 peek_sub という関数を使えばクロージャの中のレキシカル変数の内容を取り出せるらしい これでクロージャの中身無理やり覗けばなんかいけそう

Slide 10

Slide 10 text

Mouse::Util::TypeConstraints ではenum 型の型制約をどのように実装しているのかみてみます sub enum { sub enum { my($name, %valid); my($name, %valid); if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){ if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){ $name = shift; $name = shift; } } %valid = map{ $_ => undef } %valid = map{ $_ => undef } (@_ == 1 && ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_); (@_ == 1 && ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_); # EnumType # EnumType return _define_type 1, $name => ( return _define_type 1, $name => ( as => 'Str', as => 'Str', optimized_as => sub{ optimized_as => sub{ return defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]}; return defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]}; }, }, ); ); } }

Slide 11

Slide 11 text

%valid というハッシュを作り、それを型制約のチェックに利用している なるほどクロージャから変数 %valid の内容を持ってきてそのキーを全て取得すれば目的を達成 できる!!

Slide 12

Slide 12 text

やってみましょう use v5.28; use v5.28; use warnings; use warnings; use utf8; use utf8; use Mouse::Util::TypeConstraints qw( enum find_type_constraint ); use Mouse::Util::TypeConstraints qw( enum find_type_constraint ); use DDP +{ deparse => 1, use_prototypes => 0 }; use DDP +{ deparse => 1, use_prototypes => 0 }; enum RGBColors => qw( red green blue ); enum RGBColors => qw( red green blue ); my $type_constraint = find_type_constraint('RGBColors'); my $type_constraint = find_type_constraint('RGBColors'); use PadWalker qw( peek_sub ); use PadWalker qw( peek_sub ); my $valid = peek_sub($type_constraint->{compiled_type_constraint})->{'%valid'}; my $valid = peek_sub($type_constraint->{compiled_type_constraint})->{'%valid'}; p [ keys %$valid ]; p [ keys %$valid ];

Slide 13

Slide 13 text

できた!!!

Slide 14

Slide 14 text

このように PadWalker を使えばレキシカル変数の内容を無理やり覗くことができます Making Easy Things Easy and Hard Things Possible を体現するすばらしいモジュー ルですね! 黒魔術たのしい^q^

Slide 15

Slide 15 text

しかしプロダクトにこのようなコードを入れると混乱を招くこと必至・・・ 個人的にはツールやシュガー関数などの作成目的以外で PadWalker を使うのは基本避けたほうが いいと思います 今回のケースだと独自型を定義してるモジュールに定数を作るなどするなどして、それを呼ぶ ようにするのが筋がいい方法でしょうか

Slide 16

Slide 16 text

ご清聴ありがとうございました