Perlはご存知の通りTrue/Falseと言った真偽値を扱うBooleanな型は標準で備わっていない。例えば以下のテストは偽の値と解釈され全てFailする。
use Test::More; ok(0); # Fail ok(undef); # Fail ok(''); # Fail done_testing;
つまり
- 0
- undef / 未定義値
- 空文字列
が偽として解釈される。普段はこうした挙動で問題は無いんだけど、キャッシュの制御の時に困ってしまい考えた挙げ句、解決策のような実装が見つかったので紹介とツッコミいただきたい次第。
追記
以下、ふつーに defined でごにょればイケる!
unless(defined $value) { ...; }
koba04さん、toku_bass さんあざっす。とりま自分のアプリで試してみます。
キャッシュの常套手段的にこんなフローをよく使う。
- キャッシュにヒットしたらその値をそのまま使う
- とって来た値が偽だったら算出するメソッドを発行する
- 算出された値をキャッシュにsetする
うんで、実装はこんな感じ。
my $value = $cache->get('key'); unless($value) { $value = get_value(); $cache->set('key', $value); } say $value;
で、問題になるのは上のコードで言う「get_value」サブルーティンが「空」など偽と判断される値を返す時。さすればせっかく値をsetしているのに再びgetしたタイミングにこのコードだと値が偽であることからunlessブロックに入り、再度「get_value」が呼ばれることになる。「get_value」を呼び出すためのコストがかかる場合は一度「空」が返却されたんだからそれを覚えておいてもらいたい。つまり
「空であること」自体をキャッシュさせたい
ってことになる。そこで使ってみたのが Ingy の boolean.pm 。Perl標準のコンテキストとは別にTrue/False の真偽につかえる値を提供する。とりあえず「false」だけを使う。
use boolean; my $value = boolean::false; say ref $value; # boolean say $value == boolean::false ? 'false' : 'true'; # false
falseで特別に偽を定義することが出来るので、キャッシュの件だと、falseを値としてsetしちゃって、後ほどgetする時に判断させれば「空であること」自体をキャッシュさせることが出来そう。こんな感じのコードになった。
use Cache::Memcached::Fast; use feature qw/say/; use boolean; my $cache = Cache::Memcached::Fast->new({ servers => ['127.0.0.1:11211'], namespace => 'example:', }); my $value = $cache->get('key'); if(ref $value eq 'boolean' && $value == boolean::false) { say "Result: value is false"; }elsif($value) { say "Result: value is $value"; }else{ $value = make_value(); $value = boolean::false unless $value; $cache->set('key', $value, 60); say "Set: value as $value"; say "Result: value is $value"; } sub make_value { say "Called: make_value method"; return; # return null }
一回目の実行結果。
Called: make_value method Set: value as 0 Result: value is 0
このコンテキストだと「0」になっているけど、boolean::falseがsetされているはず。もっかい実行してみる。
Result: value is false
ちゃんとfalse = 空であると認識している。これでキャッシュの有効期限内だったら空が帰って来たよーという条件の元ごにょごにょ出来そう。boolean.pm の「boolean」とか「isTrue」「isFalse」のメソッドは使ってないけどとりあえずこれでよい気がするなー!