Perlで使える変なFizzBuzzの解

かのFizzBuzz問題をPerl 5、Perl 6で解いてみた。
FizzBuzz問題とは、1から100までの数字を表示する中で、
3の倍数のときFizz、5の倍数のときBuzzを表示し、どっちも当てはまるならFizzBuzzと表示しろ。
というで非常に有名なプログラミングのテストです。
まぁ普通に考えればこうなりそう。

for(1..100) {
if($_%3==0) {
print "Fizz";
if($_%5==0) { print "Buzz"; }
}elsif($_%5==0) {
print "Buzz";
}else{
print $_;
}
print "\n";
}

ちょっとめんどくさいですね。
自分の得意な方法で実現できればいいですね。Cでもいいし。


というわけで三項演算子を使ってみた。
この方法のよいところは1行で書けるから{ }がかなり省略できてスマート。

print ($_%3==0 ? 'Fizz' . ($_%5==0 ? 'Buzz' : '') : ($_%5==0 ? 'Buzz' : $_) , "\n") for(1..100) #Perl 5
say $_%3==0 ?? 'Fizz' ~ ($_%5==0 ?? 'Buzz' !! '' ) !! ($_%5==0 ?? 'Buzz' !! $_ ) for 1..100 #Perl 6

わかりにくくなったという人もいるかもしれんけどね。
これでも読みやすくするために必要以上の括弧を使って、演算子付近は空白入れたけどね。
Perl 5の方は変な括弧入ってるけど、演算子の結合順位の都合。
そうそうPerl 6では三項演算子がA ?? B !! Cとなります。
さて、三項演算子の考え方だが、
($_%5==0 ?? ‘Buzz’ !! $_ )と書けば、その中身が$_%5==0の場合は’Buzz’に、それ以外は$_に変化すると。
そう考えると理解しやすくなる。だから括弧で囲んでるのね。
しかし、腑に落ちないところがある。
($_%5==0 ?? ‘Buzz’ !! ”)ってなんか省略表記ないんかいと。
ExcelのIF関数と三項演算子は似てると言われるのだが、これはIF(A1=1,10)と書ける。
まぁ省略してどうすんのというのはあるけど。
けど、なんか真のときはこの値で、それ以外はundefとかできんのかな…


と思ってたら、さすがPerl、変な方法がありましたよ。

say ( ($_%3==0 && 'Fizz')~($_%5==0 && 'Buzz') )|| $_ for 1..100; #Perl 6

なんというか不気味ですね。
&&またはand演算子、||またはor演算子。
この2つの演算子は本当に不気味です。
or die; and die;という使い方。これはそれぞれ返り値0、1のときはエラーなので死ぬと。
これを使って、 m/\d+/ and print ‘Numeric’; とかいうコードがどっかにあったな。
まぁしかしこれについてはif文を使え。(参考記事 「open or dieというのは脅迫ではないよと言いたい」)
で、今回の使い方はこれとは違う使い方。わかりやすい||の方から説明。
$a || ‘Empty’ と書く。
この$aが長さ1の文字列だったら真か偽で言うと偽になります。それ以外は全部真。
こういう短絡論理演算子は1つ目で真なら後ろは評価しないが、偽なら後ろを評価する。
結果、この計算結果は$aが長さ0ならEmptyという文字列、そうじゃなければそのまま。
ただし! この方法は欠陥ありすぎる。0という数字、”0″という文字列、ともに評価は偽。
普通 短絡論理演算子の結果ってTrueかFalseのどっちかだけだと思うんだが、Perlは違う。
なぜかPerlではよく使われる変な方法です。
余談だがPHPの変な話。if($_GET[‘foo’]) do_foo($_GET[‘foo’]);とかよくあるけど、
上記の理由で問題あるかも。空文字でないかは”との比較、定義されてるかはisset関数でどうぞ。
同様のことが&&演算子でも使える。($_%3==0 && ‘Fizz’)という風に使ってる。
上同様に考えれば、$_%3==0の結果が偽なら$_%3==0の結果、真なら’Fizz’。
さて、偽である$_%3==0の結果はなんだ。Perl 6ならBool::Falseなんだが、Perl 5では?
実は ” なんですよ。空文字列なんだよね。真なら1という数値。
なのでPerl 6でもBool::Falseを文字列として取り扱う、~Bool::Falseとかすると空文字列。
だから、$_%3==0がTrueなら’Fizz’、Falseなら”というわけで、さっき言ってたことが実現できた。
まぁ気持ち悪いけどね。
Falseなら’Foo’、それ以外は”は以上の理由から||を使おうが無理。!で否定して&&でどうぞ。
上のは3の倍数なら’Fizz’、違えば”、5の倍数なら’Buzz’、違えば”を得て2つ結合、これが空文字だというなら$_。
気持ち悪いけど理解可能は範囲はここまでです。


ちなみにFizzBuzz問題のPerlにおける(まじめな)最短記録は、

say+(Fizz)[$_%3].(Buzz)[$_%5]||$_ for 1..100 #Perl 5 + use feature 'say';
say [$_%3]~[$_%5]||$_ for 1..100 #Perl 6

実はどちらもオリジナルとは違います。
Perl 5ではsayはデフォルトで使えないのでオリジナルではprintを使ってます。
が、これをperlに-lオプションをつけて、printでも改行するようにさせてる。
しかしそれはひどいので、せっかくだからsayを使って書いた。
これを単にPerl 6で書いたものが下。(Fizz)はPerl 5では(‘Fizz’)と解釈できたが、
Perl 6においては禁止なので、qw ()相当の<>を用いてと。
ちなみにPerl 6でもqとかqwとかは使えるけど、()で囲うときは(の前にスペース入れる。
qwについては<>、それ以外は違う、||とか{}で囲んでください。すれば気にしなくてOK!
さて、[$_%3]とは何かという話だが、’Fizz’という要素だけの配列の$_%3番目という意味。
0番目なら’Fizz’だが、それ以外はとなるが、Perlにおいてはundef、文字列として扱えば”ですね。
だから実質的には $_%3==0 ?? ‘Fizz’ !! undef と同じですね。まぁなんというか…


以上いろいろ書いてきたが、三項演算子にしても短絡論理演算子にしても変ですね。
まぁ三項演算子は初めの方に書いた通り、記述が条件によって変化するもの、
ととらえればまぁいいのかなと思うけど。
ただ三項演算子って使い道あんまり多くないですねとか。
短絡論理演算子は慣れればそんなもんなんだと思いますけどね。
僕も、A or B or C(プログラムではない!!)と書いてあったとき、
Aをやってみて、だめならBをやって、それでもだめならCをやる
という風に読めてしまうのが怖い。Perlのor dieってそんな感覚です。
まぁ $a||’Empty’ についても、$aが空なら||以降に投げるもんだと。
undefなら後ろに投げる、//演算子だって一緒ですよ。
今日の話題で一番意味不明なのが&&演算子ですね。
これは忘れた方が幸せかも知れん。こんなコード見ても意味わからんわ。