Share
Embed
令和最新版 Perlコーディングガイド
YAPC::Hakodate 2024でのトークです
https://yapcjapan.org/2024hakodate/
AnaTofuZ
October 05, 2024
Transcript
79 slides
-
my $self = shift; • 八雲アナグラ(id:anatofuz) • (心は)Okinawa.pmの京都在住 • 株式会社はてな
◦ システムプラットフォームチーム ◦ Webアプリケーションエンジニア • PerlとTypeScriptが主戦場 2 -
12 令和最新版コーディングガイド 1. よいこのお約束 2. try-catch 3. .pmのおまじない 4. レキシカルサブルーチン
5. 引数の型 6. 返り値の型 7. パターンマッチ 8. JSONエンコード 9. 令和最新版use 10. そして伝説へ... -
よいこのお約束 use strict; use warnings; use utf8; 14 use v5.26;
use warnings; use utf8; いにしえ 平成最後 use strict; use feature ‘:5.26’;(新機能開放 ) -
よいこのお約束 use strict; use warnings; use utf8; 15 use v5.26;
use warnings; use utf8; いにしえ 平成最後 use v5.40; use utf8; 令和最新 use warnings;(5.35以降) use builtin ‘:5.40’;(新組み込み関数開放 ) -
16 令和最新版コーディングガイド 1. よいこのお約束 2. try-catch 3. .pmのおまじない 4. レキシカルサブルーチン
5. 引数の型 6. 返り値の型 7. パターンマッチ 8. JSONエンコード 9. 令和最新版use 10. そして伝説へ... -
伝統のやり方 17 • evalブロックを使った 素朴なやり方 • 罠多い • 初学者からするとむずい ◦
文字列eval ◦ ブロックeval eval { die 'foo'; }; if ($@) { print $@; } -
18 ここから先の令和最新Perl事情 • Perl本体の開発がここ数年にないほど活発 ◦ 言語機能の拡張が非常に増えている ▪ Class, bultin •
モジュールで戦い切る、ではなくモジュールが 新機能の検証の道具も担ってきている ◦ 検証に成功すると本体に取り込まれる ◦ モダンPerlのアプローチとは別アプローチ -
最近のモジュール事情 • モジュールが跋扈していた時代からある程度の 統一になりつつある ◦ 型システム → Type::Tiny → Data::Checks?
◦ 関数定義 → Function::Parameters → signatures ◦ クラスビルダ → Object::Pad → builtin class 19 -
かつてのtry-catch 20 • Try::Tiny • try-catchっぽいブ ロックはそれぞれただ の関数呼び出しになる • 意外とハマりどころが
多い use Try::Tiny; try { die "foo"; } catch { warn "caught error: $_"; }; -
平成最後のtry-catch 21 • Syntax::Keyword::Try • Try::Tinyよりハマりど ころ少ない • try-catchがサブルーチ ンではない
use Syntax::Keyword::Try; try { die "foo"; } catch($e) { warn "caught error: $e"; } -
令和最新のtry-catch 22 • Syntax::Keyword::Try ベースの実装がbuiltinに • 5.34から実験的機能とし て追加 ◦ 5.40から実験的機能でなく
なった • v5.40すると使用可能 use v5.40; try { die "foo"; } catch($e) { warn "caught error: $e"; } -
23 令和最新版コーディングガイド 1. よいこのお約束 2. try-catch 3. .pmのおまじない 4. レキシカルサブルーチン
5. 引数の型 6. 返り値の型 7. パターンマッチ 8. JSONエンコード 9. 令和最新版use 10. そして伝説へ... -
最近までの罠 • あるモジュールのコード全文 ◦ このコードにはミスがあります ◦ どーこだ 24 package Acme::AnaTofuZ;
use strict; use warnings; sub hello { print "hello" } -
最近までの罠 • あるモジュールのコード全文 ◦ このコードにはミスがあります ◦ どーこだ 25 Acme/AnaTofuZ.pm did
not return a true value at sample.pl line 3. BEGIN failed--compilation aborted at sample.pl line 3. package Acme::AnaTofuZ; use strict; use warnings; sub hello { print "hello" } -
最近までの罠 • Perlのモジュールは最後に真の値を返さないと ならない!!! 26 Acme/AnaTofuZ.pm did not return a
true value at sample.pl line 3. BEGIN failed--compilation aborted at sample.pl line 3. package Acme::AnaTofuZ; use strict; use warnings; sub hello { print "hello" } 1; -
令和最新版Perl • use v5.38以上かuse feature qw(module_true)で真の値返さなくても動作 するように 27 package Acme::AnaTofuZ;
use v5.40; sub hello { print "hello" } -
28 令和最新版コーディングガイド 1. よいこのお約束 2. try-catch 3. .pmのおまじない 4. レキシカルサブルーチン
5. 引数の型 6. 返り値の型 7. パターンマッチ 8. JSONエンコード 9. 令和最新版use 10. そして伝説へ... -
レキシカルサブルーチン • 宣言されたブロックの中で有効なサブルーチンを定 義できる • 5.26から正式な機能 • ちゃんとしたプライベート メッドとしても使える 29
use feature 'say'; sub foo { my sub bar { say 'bar'; } bar(); } # 未定義の関数呼び出しによる例外が発生する。 bar(); -
例えばテストツールとして 30 • 従来(サブルーチンリ ファレンス)を利用 subtest 'add' => sub {
my sub subject($a, $b) { add($a, $b) } is subject(1, 2), 3; is subject(4, 5), 6; }; • 最新版 subtest 'add' => sub { my $subject = sub ($a, $b) { add($a, $b) } is $subject->(1, 2), 3; is $subject->(4, 5), 6; }; -
31 令和最新版コーディングガイド 1. よいこのお約束 2. try-catch 3. .pmのおまじない 4. レキシカルサブルーチン
5. 引数の型 6. 返り値の型 7. パターンマッチ 8. JSONエンコード 9. 令和最新版use 10. そして伝説へ... -
Perlの関数といえば • Perlの関数の引数は全て配列@_に設定される • イディオム的な処理で色々工夫 ◦ ハッシュで受け取ってキーワード引数 32 sub func1
{ my ($arg1, $arg2, %args) = @_; my $opt1 = $args{opt1}; my $opt2 = $args{opt2}; } func1('hoge', 'fuga', opt1 => 1, opt2 => 2); -
@_むずい • @_はPerl初心者には扱いが難しい • 引数のチェック機能がほぼない ◦ 数が足りないとか名前が違うとかは自分でチェック必須 33 sub func1
{ my ($arg1, $arg2, %args) = @_; my $opt1 = $args{opt1}; my $opt2 = $args{opt2}; } func1('hoge', 'fuga', opt1 => 1, opt2 => 2); -
引数の型表明 • @_使わずに引数を表現したい • CPANモジュールでいい感じのものがある ◦ Smart::Args::TypeTiny ▪ はてなでの主流 ◦
Function::Parameters • Webアプリケーションではこのあたりを使って 引数の型チェック/名前付き呼び出しをしたい 34 -
Smart::Args::TypeTiny • `引数名 => 型名`の組で関数の引数を定義できる ◦ 型は主にType::Tinyで定義される 35 use Smart::Args::TypeTiny
qw(args); use Types::Standard -types; sub foo { args $class, $label => Str, $counts => ArrayRef[Int], ; ... } -
Function::Parameters • 同じような機能のモジュールがある ◦ 型検査の無効化の方法などの差異はある • 海外ではこちらがよく使われがち 36 use Function::Parameters;
use Types::Standard -types; fun foo(Str :$label, ArrayRef[Int] :$counts) { return $label; } -
令和最新版関数の引数 • @_から型と組み合わせて定義する方向に ◦ Type::Tinyとの組み合わせ力💪 ◦ Perlコードによって型チェックと引数処理が行われるの で、実行時チェックが基本 ▪ 本番環境では速度のためにチェックを切るなども可能
• Function::Parametersの成果がPerl本体に取 り込まれつつある 37 -
サブルーチンシグネチャ機能 • 5.36から実験的扱いでなくなったコア機能 • 引数が他の言語の関数定義っぽく書ける ◦ 関数の個数の保証 ◦ 足りなかった引数のデフォルト値のセット •
型のチェックはできない ◦ それくらいの粒度で良ければ便利 38 sub foo ($first_name, $surname, $nickname = $first_name) { print "$first_name $surname is known as \"$nickname\""; } -
未来の関数シグネチャ • PPC 0024で Named Parameter が議論されている ◦ 将来的に入るかも 39
sub func ( :$abc, :$xyz, ... ) { ... } func( abc => 123, %args, xyz => 789, ); -
40 令和最新版コーディングガイド 1. よいこのお約束 2. try-catch 3. .pmのおまじない 4. レキシカルサブルーチン
5. 引数の型 6. 返り値の型 7. パターンマッチ 8. JSONエンコード 9. 令和最新版use 10. そして伝説へ... -
かつての俺達の関数の返り値 • 温かみのあるコメントで表現 • 機械的なチェックが無いので表記ゆれがち ◦ 実装の修正にコメントの追従が漏れるケースも • 人間がわかる以上の恩恵がない 42
# id の配列からユーザを取得する # returns: HashRef[id => Model::UserAccount] sub find_by_ids { args my $class => 'ClassName', my $db => 'AnaTofuZ::DBFactory', my $ids => 'ArrayRef[Int]'; -
関数の返り値のコメント • テストなどでも活用したい • アノテーション形式のCPANモジュール ◦ Function::Return ◦ Return::Type •
コメント文化からはギャップがある ◦ 別のアプローチでの返り値チェック機能を実装 43 use Function::Return; use Types::Standard -types; sub foo :Return(Int) { 123 } sub bar :Return(Int) { 3.14 } -
返り値の表明 44 # id の配列からユーザを取得する # returns: HashRef[id => Model::UserAccount]
sub find_by_ids { args my $class => 'ClassName', my $db => 'AnaTofuZ::DBFactory', my $ids => 'ArrayRef[Int]'; -
返り値の表明 45 use Types::Standard; # id の配列からユーザを取得する # returns: HashRef[id
=> Model::UserAccount] sub find_by_ids { args my $class => 'ClassName', my $db => 'AnaTofuZ::DBFactory', my $ids => 'ArrayRef[Int]'; -
返り値の表明 46 use Types::Standard; # id の配列からユーザを取得する res find_by_ids =>
Map[PositiveInt,InstanceOf['Model::UserAccount']]; sub find_by_ids { args my $class => 'ClassName', my $db => 'AnaTofuZ::DBFactory', my $ids => 'ArrayRef[Int]'; -
返り値の表明 47 use Types::Standard qw(Int); # id の配列からユーザを取得する res find_by_ids
=> Map[PositiveInt,InstanceOf['Model::UserAccount']]; sub find_by_ids { args my $class => 'ClassName', my $db => 'AnaTofuZ::DBFactory', my $ids => 'ArrayRef[Int]'; 「res 関数名 => 返り値の型」 返り値を返す際に型チェック -
resを使った返り値の表明 49 find_by_ids 通常実行 メインルーチン res経由 find_by_ids res (Type::Tinyによる型 チェック)
メタプログラミングで resの対象の関数をラップ する -
返り値チェック 50 • Type::Tinyの型を使って返り値チェック ◦ 自動的に書式が統一化される • 従来のコメントでの型定義からのスムーズな移行 • Perlのメタプログラミング活用
◦ 環境変数でテスト環境が指定されていた場合のみ チェックが有効化 ◦ 本番環境ではチェックフローがスキップされる -
ビジネスロジックの例 51 use constant userIdToScores => Map [ PositiveInt, PositiveOrZeroInt,];
res calculate_ranking => Dict [ users => ArrayRef [ InstanceOf ['Game::Model::User'] ], user_id_to_scores => userIdToScores, ]; sub calculate_ranking { args my $class => 'ClassName', my $db => 'Game::DBFactory', my $datetime => 'Game::DateTime', ; ある程度複雑な型構造 (入れ子のハッシュ形式) -
ビジネスロジックの例 52 use constant userIdToScores => Map [ PositiveInt, PositiveOrZeroInt,];
res calculate_ranking => Dict [ users => ArrayRef [ InstanceOf ['Game::Model::User'] ], user_id_to_scores => userIdToScores, ]; sub calculate_ranking { args my $class => 'ClassName', my $db => 'Game::DBFactory', my $datetime => 'Game::DateTime', ; Smart::Args::TypeTinyでの 引数の型宣言 -
ビジネスロジックの例 53 use constant userIdToScores => Map [ PositiveInt, PositiveOrZeroInt,];
res calculate_ranking => Dict [ users => ArrayRef [ InstanceOf ['Game::Model::User'] ], user_id_to_scores => userIdToScores, ]; sub calculate_ranking { args my $class => 'ClassName', my $db => 'Game::DBFactory', my $datetime => 'Game::DateTime', ; -
54 令和最新版コーディングガイド 1. よいこのお約束 2. try-catch 3. .pmのおまじない 4. レキシカルサブルーチン
5. 引数の型 6. 返り値の型 7. パターンマッチ 8. JSONエンコード 9. 令和最新版use 10. そして伝説へ... -
型によるパターンマッチ 55 • 型によるパターンマッチ ◦ 直和型も作成可能 use constant Movie =>
Dict[]; use constant Comic => Str & sub { $_ eq 'comic' }; use constant Media => Movie | Comic; sub to_label { my $self = shift; state $kind_to_label = compile_match_on_type( Movie, sub { '映画化' }, Comic, sub { '漫画化' }, ); $kind_to_label->($self->kind); #映画化 or 漫画家 } -
56 令和最新版コーディングガイド 1. よいこのお約束 2. try-catch 3. .pmのおまじない 4. レキシカルサブルーチン
5. 引数の型 6. 返り値の型 7. パターンマッチ 8. JSONエンコード 9. 令和最新版use 10. そして伝説へ... -
平成のJSONエンコード 58 • JSON::XSとJSON::Typesの合せ技 ◦ JSON::XSでは最後に変数が評価されたコンテキス トでJSONの型を決定する use JSON::XS; use
JSON::Types (); my $json_str = encode_json({ id => JSON::Types::number($id), is_boolean => JSON::Types::bool(true), id_str => JSON::Types::string($id), }); # {"is_boolean":true,"id":0,"id_str":"0"} -
平成のJSONエンコード 59 • JSON::XSとJSON::Typesの合せ技 ◦ JSON::Typesで強制したい型のコンテキストで変 数を評価する use JSON::XS; use
JSON::Types (); my $json_str = encode_json({ id => JSON::Types::number($id), is_boolean => JSON::Types::bool(true), id_str => JSON::Types::string($id), }); # {"is_boolean":true,"id":0,"id_str":"0"} -
平成のJSONエンコード 60 • encode_jsonの直前にJSON::Typesを呼ぶ ◦ JSON::Typesとencode_jsonの間に変数に触れる と予期せぬ状態になりがち use JSON::XS; use
JSON::Types (); my $json_str = encode_json({ id => JSON::Types::number($id), is_boolean => JSON::Types::bool(true), id_str => JSON::Types::string($id), }); # {"is_boolean":true,"id":0,"id_str":"0"} -
令和最新のJSONエンコード 61 • Cpanel::JSON::XSを採用 ◦ encode_jsonの第二引数に型情報を渡せる ▪ Type::TinyからCpanel::JSONの型変換 use Cpanel::JSON::XS;
use constant JsonType => Dict[ id => PositiveInt, is_boolean => Bool, is_str => Str, ]; my $type = type_to_cpanel_type(JsonType); say encode_json({ id => '42', is_boolean => false, is_str => 42 }, $type); -
令和最新のJSONエンコード 62 • JSONライブラリ側で型変換してくれるので安心感 ++ ◦ 型情報もType::Tinyのものを使い回せる ▪ 見通しのよさ++ use
Cpanel::JSON::XS; use constant JsonType => Dict[ id => PositiveInt, is_boolean => Bool, is_str => Str, ]; my $type = type_to_cpanel_type(JsonType); say encode_json({ id => '42', is_boolean => false, is_str => 42 }, $type); -
63 令和最新版コーディングガイド 1. よいこのお約束 2. try-catch 3. .pmのおまじない 4. レキシカルサブルーチン
5. 引数の型 6. 返り値の型 7. パターンマッチ 8. JSONエンコード 9. 令和最新版use 10. そして伝説へ... -
useお約束セットができてきた 64 • Type::Tinyは便利だけど use必須 ◦ あらゆる場所で useしまくらないといけないの大変 • use
v5.40も便利だけれど ◦ アプリの全ファイルでバージョン指定するの大変 ▪ Perlのバージョン変えたら一箇所だけ変更する PRを送って解決したい -
プラグマ • プラグマは、strict や warnings のように、コ ンパイル時や実行時の Perl の 振る舞いにある
種の影響を与えるモジュールです ◦ https://perldoc.jp/docs/perl/5.40.0/perlpragma.pod • 組み込みプラグマ以外に自作も可能 65 -
use ful; 67 • チームに名前を公募して 決定 • 以下と等価 ◦ use
v5.40; ◦ use Types::Standard -types; ◦ use utf8; • 類似品にModern::Perl モジュール package Perl::Example; use ful; use Carp qw(croak); … -
use ful;でできること • try-catch • 型を使った表現 ◦ 引数 ◦ 返り値
• 組み込みclass, 組み込みtrue,false … ◦ useするだけで令和最新版のPerlへの道が開かれる 68 -
69 令和最新版コーディングガイド 1. よいこのお約束 2. try-catch 3. .pmのおまじない 4. レキシカルサブルーチン
5. 引数の型 6. 返り値の型 7. パターンマッチ 8. JSONエンコード 9. 令和最新版use 10. そして伝説へ... -
よいこのお約束 use strict; use warnings; use utf8; 70 use v5.26;
use warnings; use utf8; いにしえ 平成最後 use v5.40; use utf8; 令和最新 -
よいこのお約束 use strict; use warnings; use utf8; 71 use v5.26;
use warnings; use utf8; いにしえ 平成最後 use v5.40; use utf8; 令和最新 use ful; # これが真・令和最新版 -
まとめ • 令和最新版Perl 5.40での書き方をみた ◦ 思ったより現代化していると思っていただければ ◦ TMTOWTDIなので別のやり方も全然アリだと思います ◦ 今もPerlはuseful
• @kfly8++ ◦ 同じチームで開発していたときに色々やってました 72 -
74 コーディング以前 • 環境構築系 ◦ 言語バージョン管理 ◦ パッケージ管理 • 開発支援
◦ LSP ◦ Linter ◦ Formatter -
75 コーディング以前 • 手元で複数バージョン切り替えたい ◦ perlblew ◦ plenv ◦ mise
• docker imageはdocker-perlリポジトリで管理 ◦ cpanm/cpmがデフォルトでバンドルされてる ◦ mod_perlはmotemenさんが趣味で作ってる -
76 パッケージ管理 • Carmel ◦ cpanfileを使ったバージョン管理 ◦ Cartonの後継 ▪ キャッシュシステムもあり爆速
• cpm ◦ 並列でモジュールインストールをするので高速 ◦ Carmelと同様にcpanfileにも対応 ◦ cpmでcpmをinstallすることも可能 -
77 PerlNavigator • かなり出来がいいLSP • 主戦場はVSCodeだが他のエディタでも使える • コードジャンプやオブジェクトの推論も ◦ プロジェクトのCPANモジュールをエディタが動いている
環境にインストールする必要がある • linter/formatterとの連携もある -
78 パッケージ管理 • Perl::Critic ◦ いわゆるLinter ◦ 自分でPolicy(ルール)を記述することも可能 • Perl::Tidy
◦ いわゆるFormatter • App::perlimports ◦ golangのgoimports的な開発支援ツール