Masteries

技術的なことや仕事に関することを書いていきます.

小ネタ: Test2でテストを書く時に, hashやarrayにundefがあることをU()でテストすると, なんか意図しない感じになることがある

またまたまたまた小ネタです. PerlのテストモジュールであるところのTest2において, U()undef であることをテストします.

is undef, U(); # => ok
is 1, U(); # => not ok

では, ハッシュや配列にundefがあることをテストしたい時はどうするといいでしょうか?

my $array = [ 'a', undef ];
is $array, array {
    item 'a';
    item U();
    end;
};

my $hash = { a => 1, b => undef };
is $hash, hash {
    field a => 1;
    field b => U();
    end;
};

一見良さそうに見えます(テストもパスします). が, 次の場合はどうでしょう.

my $array = [ 'a' ];
is $array, array {
    item 'a';
    item U();
    end;
};

my $hash = { a => 1 };
is $hash, hash {
    field a => 1;
    field b => U();
    end;
};

なんとこちらもパスします. undef があることをテストしたくてテストを書いたはずなので, このままだといけませんね.

この場合, きちんとundefが存在することをテストするには, U() ではなく, 明示的に undef を指定して, 比較する必要があります.

my $array = [ 'a' ];
is $array, array {
    item 'a';
    item undef; # エラー
    end;
};

my $hash = { a => 1 };
is $hash, hash {
    field a => 1;
    field b => undef; # エラー
    end;
};

このへんは, HASH BUILDERのところに,

# Ensure the key exists, but is set to undef
field bat => undef;

metacpan.org

...と書かれていました. なので, 「絶対にundefであることをテストしたい!」という場合は, 前述の通り U() を使わずに常に undef でテストしてあげましょう.

小ネタ: Perlで条件に応じて配列の中身を割り振る時はList::MoreUtils#partが使える

メモです. こういう配列があったとします:

my @array = (
    { type => 'foo', value => 1 },
    { type => 'bar', value => 2 },
    { type => 'foo', value => 3 },
);

この時, typeによって@arrayの中身をそれぞれ別の配列に割り振りたい, と思う時があります.

# こうしたい
my @foo = (
    { type => 'foo', value => 1 },
    { type => 'foo', value => 3 },
);

my @bar = (
    { type => 'bar', value => 2 },
);

こういうときは, タイトルにある通りで, List::MoreUtils#part を使うのがよさそうです.

metacpan.org

use DDP;
use List::MoreUtils qw(part);

my @array = (
    { type => 'foo', value => 1 },
    { type => 'bar', value => 2 },
    { type => 'foo', value => 3 },
);

my ($foo, $bar) = part { $_->{type} eq 'foo' ? 0 : 1 } @array;

p $foo;
# \ [
#     [0] {
#         type    "foo",
#         value   1
#     },
#     [1] {
#         type    "foo",
#         value   3
#     }
# ]
p $bar;
# \ [
#     [0] {
#         type    "bar",
#         value   2
#     }
# ]

当てはまる要素がない場合は, 空配列ではなくundefになるのでその点は注意しましょう.

use DDP;
use List::MoreUtils qw(part);

my @array = (
    { type => 'foo', value => 1 },
);

my ($foo, $bar) = part { $_->{type} eq 'foo' ? 0 : 1 } @array;

p $foo;
# \ [
#     [0] {
#         type    "foo",
#         value   1
#     }
# ]
p $bar;
# undef

小ネタ: Perlのhashとデフォルト値

関数の引数をhashで受けて, 未定義の場合はデフォルト値を設定したい... という場合があるとします. まあだいたい, defined-orを使って次のように書くのではないでしょうか:

sub myfunc {
    my %args = @_;

    return $args{foo} // 'default';
}

myfunc(foo => 'yeah'); # => 'yeah'
myfunc(); # => 'default'

一方で undef を渡せるようにしたいという場合, この実装だとデフォルト値に上書きされてしまいます.

myfunc(foo => undef); # => 'default' XXX: undef が返ってきて欲しいのに...

そういうときは, exists を使って次のように書くという手が使えます:

sub myfunc {
    my %args = @_;

    return exists $args{foo} ? $args{foo} : 'default';
}

myfunc(foo => 'yeah'); # => 'yeah'
myfunc(); # => 'default'
myfunc(foo => undef); # => undef;

もし$argsがhashのリファレンスならこうですね: exists $args->{foo} ? $args->{foo} : 'default';

テストヘルパーとか作る時によく使うsnippetって感じで覚えておけると良さそうです.

Class::Accessor::Typed 0.03_02 で Type::Tiny 対応を試しています

papix.hatenablog.com

先日, 0.03を出したClass::Accessor::Typedですが, Type::Tiny対応を試した 0.03_02 をリリースしました. Development releaseなので, cpanm とかでインストールする時はバージョンを指定してインストールするようにしてください:

cpanm Class::Accessor::Lite@0.03_02

Class::Accessor::Typedはインスパイア元であるSmart::ArgsにならってMouse::Util::TypeConstraintsを使っているのですが, Smart::Args::TypeTiny派はType::Tinyを使いたいよね, となるのは自明でした. というわけで, Smart::Args::TypeTinyの実装を参考にしつつ, Mouse::Util::TypeConstraintsを使うか, Type::Tinyを使うかを差し替えられるようにしています.

デフォルトは従来どおりMouse::Util::TypeConstraintsで, 次のようにすればType::Tinyが使われます(Type::Tinyはオプションとして, Class::Accessor::Typedの依存モジュールにしていないので, 別途インストールする必要があります).

use Class::Accessor::Typed (
    type => 'TypeTiny',
    ...
);

ただ, これだと都度typeを指定する必要があるので面倒ですね. 書き忘れて意図せずデフォルト実装が使われることもありそうです. そこで, 次のエントリで紹介したような, パッケージを別途用意する方法だとシンプルに書けておすすめと思います:

papix.hatenablog.com

package MyClassAccessorTyped;

use Class::Accessor::Typed;

BEGIN { $Class::Accessor::Typed::TYPE_CLASS = "Class::Accessor::Typed::TypeTiny" }

sub import {
    goto \&Class::Accessor::Typed::import
}

1;

この辺りの, Type::Tinyを指定する方法はなんかいい方法がしっくり来ていなくて, 正式に対応するバージョンになるであろう0.04で使い方が変わる可能性がありそうです. 「こういう風になっていると嬉しそう!」というご意見などありましたら, TwitterやGitHubのIssueでコメント頂ければと思います.

github.com

Kichijoji.pmで2回くらい話していました

...が, そういえばブログエントリ書いてなかったので, 発表資料を紹介しておきます.

Kichijoji.pm #28

Kichijoji.pm #29


特に「Kichijoji.pm #28」の「ワーケーションに関する考察」については, 自分らしい(旅行が趣味なエンジニアらしい)発表になったんじゃないかな, と思っていて, ワーケーションに関する知見をうまくまとめられたと思っています. コロナ禍の影響でだいぶ「ワーケーション」という文化が普及してきた感じがあって, 「試してみたい!」という人に届いたらいいなと思います.

Kichijoji.pm, 自分にとっては実家のような場所で, いろいろなネタを持っていっても暖かく受け止めてくれる(?)ので, いい勉強会だなって思います. これからも, Kichijoji.pmを通して技術に限らないエンジニアとしてやっていく上でのいろいろな話題を共有していけたらいいなって思います.