undefの各コンテキストにおける振る舞いについて

この記事におけるperlのバージョンは5.18.0,
式の評価などは特に指定のないときperl対話シェルのReply*1上で行っているものとします。


未定義値undefははスカラ値です。
undefは単純なスカラコンテキストにおいてスカラ値undefを返します。

120> scalar undef
$res[85] = undef

121> undef
$res[86] = undef

122> my $a = undef
$res[87] = undef

123> $a
$res[88] = undef

またよく知られるようにブール値コンテキストにおいてundefが渡るときそれは必ず偽を返します。

134> (undef) ? 1 : 0
$res[92] = 0

135> if(undef){ 1 } else { 0 }
$res[93] = 0

136> unless(undef){ 0 } else { 1 }
$res[94] = 0

ここまではよく知られたundefの挙動です。
さてそのundefをいろいろ虐めてみたところこいつは結構怪しい挙動をすることがわかったのでここに記しておきます。

さて、上の例でスカラコンテキストにおいてundefはundefと書きました。これは厳密には不定コンテキストにおいてのみundefはundefとして振る舞うのです。
比較などにおいて特に数値コンテキスト、文字列コンテキストとしてundefが評価される時、数値コンテキストにおいては0,文字列コンテキストにおいては''(空文字列)として振る舞います。

144> 0 == undef
$res[101] = 1

145> '' eq undef
$res[102] = 1

ただしここで

148> '' == undef
$res[105] = 1 #true

149> 0 eq undef
$res[106] = '' #false

となかなかに不可解な動作をしますがこれは

154> ''.0
$res[110] = '0'

155> 0 + ''
$res[111] = '0'

156> 1 * ''
$res[112] = '0'

に見るように文字列コンテキストにおいて0が'0'として、数値コンテキストにおいて''が0として振る舞う(+, *, .はいずれも左結合な演算子)ためで、undefを責めるのはすこし可哀想です。

リストコンテキストにおいてundefを使う用事といえばこんなものが思いつきます。

190> my ($a, $b, $c) = undef
$res[142] = [
  undef,
  undef,
  undef
]

191> ($a, $b, $c)
$res[143] = [
  undef,
  undef,
  undef
]

これは左辺値のリストコンテキストをundef埋めする時によく使うものです。コードは行儀良く書かなければいけませんからね。
ところで同じリストコンテキストでも

192> my @a = undef
$res[144] = undef

193> @a
$res[145] = undef

194> scalar @a
$res[146] = 1

195> @a[0]
$res[147] = undef

196> @a[1]
$res[148] = undef

と少し違った動作をしますがここで忘れてはならないのが代入演算子=は右結合であり、上にあるいずれの式においても先ずundefが先に評価されるということです。
よってこれはundefによる動作ではなくスカラコンテキストにおける配列構造の性質であると捉えるべきです。

思い出してください。($a, $b, $c)は明らかにリストですが、@SOME_ARRAYは配列です。

269> @a = (4, 3, 2, 1)
$res[207] = [
  4,
  3,
  2,
  1
]

270> scalar @a
$res[208] = 4

271> scalar (4, 3, 2, 1)
$res[209] = 1

272> (1, 2, 3) == (3, 2, 1)
$res[210] = ''

273> (2, 1, 3, 2) == (1, 2)
$res[211] = 1

つまり何を言いたいかといえば上の($a, $b, $c)の例だけを見て

249> ($a, $b, $c)
$res[191] = [
  1,
  1,
  2
]

250> ($a, $b, $c) = ()
$res[192] = [
  undef,
  undef,
  undef
]

「と同じだ!そうか、リストコンテキストにおいてundefは空リストと同じだ!」などという早とちりをしないようにしようということです。

↑バカ

前述のとおり代入演算子=は右結合なのでリスト代入の時本来は左辺値であるリストの方がスカラとしての振る舞いを持つのですが、これは取りうる全てのパターンにおいて

配列またはリスト = undef

配列またはリスト = @{ [undef] } => (undef)

と等価に振る舞うと見て差し支えないはずです。

そのため

274> @a = undef
$res[212] = undef

のとき@a = (undef)であり

275> defined @a
$res[213] = 1

は真を返します。これは直感的でありません。

配列@ARRAYに対するdefined @ARRAYが偽を返すのは

  • my @ARRAY;のように単純に宣言されたとき
  • undef( @ARRAY )が明示的に呼ばれたとき

のみで
@ARRAY = (undef)やdefined @ARRAYが真の後@ARRAY = ()を実行してもdefined @ARRAYは真を返します。

ただし上記の状態defined @ARRAYが偽の状態から@ARRAY = ()のように内部で無視される式を実行してもdefined @ARRAYは偽であったため、defined @ARRAYはメモリの状態を参照している物と考えられます。

このへんのundefの挙動って把握してないとちょっと複雑なデータ構造でギミックなことをしようとしたときドツボにはまりかねないですね。
僕としてはundefを含む式の評価は全て偽、と言うかそんなものは多く意味を持たないことが多いと思うので全部undefを返して欲しいものです。
文字列コンテキスト、数値コンテキストのundefの挙動と配列評価のときなんかで一貫して「未定義値」というfalseでないと困ることって多いと思うんですけどね。undefはあくまで実行の初期段階における内部的な振る舞いしか想定しておらずユーザレイヤでギミックな使い方をするのは想定されていないんでしょうか…
殊に、文字列コンテキストと数値コンテキストのあの挙動は少々頂けないです。


本当はこの後にスマートマッチングの悪口を書くつもりだったのですがこれ以上はエントリがごちゃごちゃしそうなので別エントリにて書こうと思います。
スマートマッチングは割と好きなので気は進みませんが… 5.18.0でexperimentalになった理由が少しは実感できたような…?