この記事は、Perl Advent Calendar 2023 12日目の記事です。 11日目は@shogo82148で「ExifToolがすごいという話」でした。
アドベントカレンダーのネタを探してperldeltaをさまよい歩くいっちーです。
Perl 5.38.0 から、「シグネチャーのデフォルト式に定義性論理和と論理和が使えるようになった」という記載を見つけたので試してみます。
定義性論理和と論理和
「定義性論理和と論理和」と聞いてもピンとこなかったので、一応おさらい。
いわゆる //
演算子と ||
演算子のことです(発音できない)。
英語で defined-or, logical-or と言ったほうが馴染み深い人も多いかもしれません。
「定義性論理和」の和訳は <perldoc.jp> を参考にしました。 perlop の担当をしたひとも 日本訳に相当苦労したことでしょう・・・。
論理和
論理和はどちらか一方が真ならば真を、それ以外の場合は偽を返す演算子です。 これは他のプログラミング言語と同じなので、みなさんおなじみだと思います。
Perlがおもしろいのは true
, false
というキーワードが(まだ)ないということですね。
0
, "0"
, undef
は真、それ以外はすべて偽として認識されます。
true
, false
の代わりに 1
, 0
を使って、 ||
の挙動を確かめてみましょう。
use 5.38.0;
say 0 || 0; # 0
say 0 || 1; # 1
say 1 || 0; # 1
say 1 || 1; # 1
論理和の短絡評価
||
演算子は短絡評価を行います。
たとえば 1 || $a
という式があったら、$a
にどんな値が入っていても真なので、$a
の評価を行いません。
さらに ||
演算子は 最後に評価した値 を、そのまま式自体の値として返します。
そのままの値を返すので、たとえば以下のように文字列を ||
演算子にわたすと、そのまま文字列が返ってきます。
use 5.38.0;
say "Hello" || "World"; # Hello
say undef || "World"; # World
デフォルト値の論理和を使う
論理和のこの性質を利用して、「引数を省略したらデフォルト値を使用する」というイディオムが編み出されました。
たとえば以下のような、「名前を渡すとその人に挨拶をする関数」を考えてみましょう。 ただし、引数を省略した場合はWorldに挨拶するものとします。
use 5.38.0;
sub hello {
my ($name) = @_;
# $name = $name || "World"; と同じ
# $nameが省略された場合 "World" を代入する
$name ||= "World";
say "Hello, $name!";
}
hello("Shogo"); # Hello, Shogo!
hello(); # Hello, World!
定義性論理和
しかしこの関数には重大なバグがあります。 「0」さんに挨拶できないのです!
use 5.38.0;
sub hello {
my ($name) = @_;
# "0" は偽として判断されるので、"World"が代入される
$name ||= "World";
say "Hello, $name!";
}
hello("0"); # Hello, World!
このような問題に対応するために、undef
のみを偽として扱う演算子が導入されました。
それが定義性論理和 //
演算子です。
use 5.38.0;
sub hello {
my ($name) = @_;
$name //= "World";
say "Hello, $name!";
}
hello("0"); # Hello, 0!
やったね、「0」さん!
シグネチャのデフォルト式を使う
現代のPerlは 関数に引数リストを持てる!! ので、以下のように書き直すことができます。
use 5.38.0;
sub hello($name = "World") {
say "Hello, $name!";
}
# 名前を渡すと挨拶してくれる
hello("Shogo"); # Hello, Shogo!
# 名前を省略するとデフォルト値を使ってくれる
hello(); # Hello, World!
# "0" も正しく扱える
hello("0"); # Hello, 0!
シグネチャのデフォルト式に論理和を使う
万能に思えるデフォルト式ですが、ひとつ問題があります。
「デフォルト値を指定しない」という意図で undef
を渡すと、意図した通りには動きません。
use 5.38.0;
sub hello($name = "World") {
say "Hello, $name!";
}
hello("Shogo"); # Hello, Shogo!
hello(); # Hello, World!
hello(0); # Hello, 0!
# デフォルト値を使ってほしいのに、 $name = undef として実行されてしまう
hello(undef); # Use of uninitialized value $name in concatenation (.) or string at perlsub.pl line 4.
(ここまでが前置き、ここから本題)Perl 5.38からは、以下のように関数を定義することで解決できます。
use 5.38.0;
sub hello($name //= "World") {
say "Hello, $name!";
}
hello("Shogo"); # Hello, Shogo!
hello(); # Hello, World!
hello(0); # Hello, 0!
# デフォルト値を使ってくれる
hello(undef); # Hello, World!
まとめ
Perl 5.38から以下のような関数定義が可能になりました。
use 5.38.0;
sub hello($name //= "World") {
say "Hello, $name!";
}
hello(undef)
みたいな呼び出し方だとありがたみを感じませんが、変数を渡してあげると便利かもしれませんね。
use 5.38.0;
sub hello($name //= "World") {
say "Hello, $name!";
}
hello($ARGV[0]);
明日13日目は@doikojiで「低レベルPerlスクリプトのススメ ~ 「readtsv」の紹介」です。 お楽しみに!