Slide 1

Slide 1 text

Perl でも関数の型をチェックしたい Japan.pm 2021 id: ybrliiu

Slide 2

Slide 2 text

自己紹介 ● id: _ybrliiu / mp0liiu ● 所属 : 株式会社モバイルファクトリー ● 普段はPerl書いています ● 最近は古くなったフロントエンドの エコシステムをアップデートする仕事をしています

Slide 3

Slide 3 text

突然ですが 次のコードを見てどう思いますか?

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

インスタンス変数の型に サブルーチンリファレンスの型を指定

Slide 6

Slide 6 text

コールバックに渡される引数、 期待する返り値が何なのかわからん!

Slide 7

Slide 7 text

期待していないような値を返す コールバックが渡されたらどうしよう?

Slide 8

Slide 8 text

コールバック関数に型がついてほしい

Slide 9

Slide 9 text

このようになってほしい

Slide 10

Slide 10 text

このようになってほしい コールバックに渡される引数、 期待する返り値が明示されている

Slide 11

Slide 11 text

Perlにおける型制約事情 ● 動的型付け言語なので型チェックは動的に行われる ● 外部にインターフェースを公開する部分はしばしば型チェックが行われて いる ○ インスタンス変数や関数の引数の型など ○ 型制約を設けることによるメリットが多い

Slide 12

Slide 12 text

具体例 1. クラスビルダーによる型チェック 2. 引数バリデーターによる型チェック 3. 型制約ライブラリ

Slide 13

Slide 13 text

クラスビルダーによる型チェック ● クラスビルダー ○ オブジェクト指向関連の実装を簡単に記述できるライブラリ ○ e.g.) C::A::L, Moose, Mouse, Dios, Zydeco, etc... ● 組み込みで型制約システムを持っているクラスビルダーがある ● アトリビュートを作るとインスタンス変数の型をチェックしてくれる

Slide 14

Slide 14 text

Mooseによるインスタンス変数の型チェックの例

Slide 15

Slide 15 text

引数バリデーターによる型チェック ● CPAN には引数をチェックする様々なモジュールがある ○ e.g.) Params::Validate, Data::Validator, Smart::Args, Type::Params, Function::Parameters etc... ● クラスビルダー組み込みの型制約システムや、型制約ライブラリと組 み合わせて使うことで引数の型をチェックできる

Slide 16

Slide 16 text

Smart::Args による引数の型チェックの例

Slide 17

Slide 17 text

型制約ライブラリ ● 型チェックする機能だけを提供するモジュール ○ e.g.) Type::Tiny, Specio ● 再利用性が高い ● 最近は Type::Tiny とクラスビルダーや引数バリデーターと組み合わ せて使うことがトレンド ● 同梱の Types::Standard で提供されている型でほとんどのユース ケースをカバーできる

Slide 18

Slide 18 text

Type::Tiny による型チェックの例

Slide 19

Slide 19 text

Perlの関数型の現状 ● どの型制約ライブラリやクラスビルダーを探しても、コードリファレンス であるかをチェックする CodeRef 型しか存在しない ● 関数の引数の型や返り値の型をチェック / 明示したくてもできない ○ 他の型をしっかり書いていても関数の部分だけ割れ窓になる

Slide 20

Slide 20 text

ないなら実装するぞ!!!

Slide 21

Slide 21 text

実装するにあたって必要なもの ● 関数の型情報を付与 / 取得できる仕組み ● 2つの関数の型情報を比較する仕組み ● 関数の型情報を比較する型

Slide 22

Slide 22 text

関数の型情報を付与 / 取得できる仕組みへの要求 ● CodeRef の引数の型と返り値の型情報を付与し、それらをチェックす る ● 後から引数の型と返り値の型情報を取得できるようにする ● 引数の型と返り値の型情報は CodeRef のスコープが外れたら破棄 して欲しい ● 実行時のオーバーヘッドをなるべく減らしたい

Slide 23

Slide 23 text

Sub::WrapInType の実装 ● 前述の要求を満たすものがなかったので実装 ● wrap_sub 関数に CodeRef、引数の型、返り値の型を与えると、与え られた CodeRef を引数の型と返り値の型をチェックする処理でラップ したCodeRefを返す

Slide 24

Slide 24 text

Sub::WrapInType の利用例

Slide 25

Slide 25 text

● wrap_sub で生成される CodeRef は bless されたオブジェクト ○ 関数の型の情報はスコープが外れたら破棄される ○ CodeRef をそのまま実行できる ■ ハッシュベースのクラスで演算子オーバーロードする場合と比 べてコード実行時のオーバーヘッドが少ない ● Inside-out object というテクニックでクラスを実装 Sub::WrapInType の実装

Slide 26

Slide 26 text

実装するにあたって必要なもの ● 関数の型情報を付与 / 取得できる仕組み ● 2つの関数の型情報を比較する仕組み ● 関数の型情報を比較する型

Slide 27

Slide 27 text

2つの関数の型情報を比較する仕組み Sub::Meta を利用する

Slide 28

Slide 28 text

実装するにあたって必要なもの ● 関数の型情報を付与 / 取得できる仕組み ● 2つの関数の型情報を比較する仕組み ● 関数の型情報を比較する型

Slide 29

Slide 29 text

関数の型情報を比較する型の実装 ● Sub::WrapInType で関数の型の情報を付与した CodeRef を Sub::Meta で比較する型を実装する ● ポータビリティを考慮し Type::Tiny で型を実装する ○ 様々なクラスビルダーや引数バリデーターと組み合わせて利用で きる

Slide 30

Slide 30 text

Type::Tiny での独自型の実装 ● Type::Tiny のコンストラクタに値をチェックする処理、親にあたる型、 型名、型強制する場合の処理などを渡して実装する ○ Type::Utils のユーティリティ関数を用いれば簡単 ● 型オブジェクトを返す関数を作ってエクスポートする ○ Type::Library を利用すると簡単

Slide 31

Slide 31 text

Type::Tiny での独自型の実装例

Slide 32

Slide 32 text

総称型 ● 関数の型情報を比較する型は関数の型情報をパラメータにとる総称 型になる ● 総称型とは ○ 抽象化された型で、型パラメータを渡すと具体化する ○ Type::Tiny でいう ArrayRef[T], HashRef[T] など

Slide 33

Slide 33 text

Type::Tiny での総称型の実装 ● 独自の総称型を簡単に実装できるユーティリティは存在しない ● 型パラメータが渡されたとき型名、型制約、型強制がどのように具体化 されるかを定義する ○ name_generator, constraint_generator, coercion_generator ● 上記のパラメータを Type::Tiny に渡してインスタンスを生成 ● 型パラメータリストを ArrayRef で取り型を具体化する関数を作る

Slide 34

Slide 34 text

Type::Tiny での総称型の実装例

Slide 35

Slide 35 text

Type::Tiny での総称型の実装例

Slide 36

Slide 36 text

Type::Tiny での総称型の実装例

Slide 37

Slide 37 text

実装するにあたって必要なもの ● 関数の型情報を付与 / 取得できる仕組み ● 2つの関数の型情報を比較する仕組み ● 関数の型情報を比較する型

Slide 38

Slide 38 text

関数の型をチェックする型 Types::TypedCodeRef が完成!

Slide 39

Slide 39 text

Perlで関数の型チェックを実現

Slide 40

Slide 40 text

この型を使って最初のコードを書き換え てみましょう

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

どんなコールバック関数を渡せばいいかひ と目でわかる!!!

Slide 43

Slide 43 text

型強制もできる

Slide 44

Slide 44 text

型強制もできる CodeRef を渡すと Types::TypedCodeRef の型情報を使い Sub::WrapInType でラップしてくれる

Slide 45

Slide 45 text

DEMO

Slide 46

Slide 46 text

今後の展望 ● inline 化してパフォーマンス良くしたい ○ 型チェックするコードを文字列化して結合し eval することで、関数 呼び出しのオーバーヘッドがなくなる ● エラーメッセージをわかりやすくしたい ○ 現状急に「型チェックに失敗した!」みたいなエラーがでてくるの で何が原因でエラーになったのかわかりにくい

Slide 47

Slide 47 text

まとめ

Slide 48

Slide 48 text

Types::TypedCodeRef で コールバックの型をつけよう!

Slide 49

Slide 49 text

Any Questions?