この記事にはいくつか誤りがあることが分かったので,全面的に書き直す予定です。ごめんなさい。
この記事は Ruby 初心者向け。
Ruby で,x
にマトモな値が入っていない場合だけ何かを代入したい,というときに
x ||= 1
とかって書くよね。「マトモな値」というのは,ここでは〈真〉と評価される値のことを指している。
ちなみに Ruby では nil と false 以外のすべてのオブジェクトが〈真〉と評価される。
解釈
この文は
x || (x = 1)
と解釈されることになっている。
読者の中には,「えっ?
x = x || 1
じゃないの?」と思う人もいるかもしれないけど,そうじゃない。その話はあとでするね。
||
という演算子
||
という二項演算子は,OR 条件の論理演算を行う。
式1 || 式2
を評価した結果は,式1
と 式2
の少なくとも一方が〈真〉のときに〈真〉となり,式1
と 式2
が両方とも〈偽〉のときに〈偽〉となる。
こんな疑問が湧かないかな?
でもさ,評価結果が「〈真〉となる」とか「〈偽〉となる」とか言ったってさ,具体的には何になるのさ?
もっともだ。Ruby では式の値はすべて何らかのオブジェクトになるわけだから,どういうオブジェクトなんだよ?っていう疑問だね。
じゃあ,言い直そう。
式1 || 式2
を評価した結果は,式1
が〈真〉のときは 式1
の評価結果となり,式1
が〈偽〉のときは 式2
の評価結果となる。
ちょっと待てよ,さっきと全然違うじゃねーか。
いや,言い方は違うけど,これが OR 条件の評価になっていることは,式1
や 式2
に nil だの 3.14 だのを入れて真/偽の組合せをいろいろやってみれば分かる。
# 偽 OR 偽 → 偽 の例
puts nil || false #=> false
# 偽 OR 真 → 真 の例
puts false || :hoge #=> :hoge
# 真 OR 偽 → 真 の例
puts "" || nil #=> ""
# 真 OR 真 → 真 の例
puts [] || 3.14 #=> []
実行
では
x || (x = 1)
がどのように実行されるかを見てみよう。
x
が〈偽〉と評価される値を持っている場合,x = 1
が評価される。これによって x
に 1
が代入されるわけだ。
この場合,式全体の評価値は 1
となる。なぜなら式 x = 1
の評価値が 1
だからだ。あ,ちなみに Ruby では代入も〈式〉の一種で,値を持つんだからね。
一方,x
が〈真〉と評価される値を持っている場合,x = 1
は評価しない。そして式全体の評価値は x
の評価値となる。
x
が未定義の場合
実は x
に 1
が代入されるのは,x
が偽のときだけではない。x
が未定義の場合も代入が行われる。
だから,
x ||= 1
puts x
とだけ書いたスクリプトは,期待通り 1 を表示してくれる。
しかし,あなたはまたこんな疑問を持つかもしれない:
x
が未定義だったらx || (x = 1)
を評価しようとして NameError を出すはずでは?
その疑問ももっともだ。その前に,NameError って何だったっけ。
NameError
x
が未定義のときに
puts x
を実行しようとすると,
NameError: undefined local variable or method `x' for main:Object
みたいなエラーが出る。
Ruby の処理系にとって,x
はとりあえず〈識別子〉であるわけだ。小文字で始まっているので,可能性としては①ローカル変数の参照と,②メソッド呼出しの二つが考えられる。
※Ruby は引数無しでメソッドを呼び出すとき,( )
を省略してメソッド名だけを書いてもよいので,puts x
の x
は,メソッド x
を呼び出しているの かも しれないのだ。
しかし,x
というメソッドが定義されていなくて,x
というローカル変数も知らない場合はエラーとなる。これが「undefined local variable or method」というメッセージの意味するところだ。
なぜ NameError にならないのか
この章には誤りがあるかもしれません。あとで調べます。次章には誤りがあります。
Ruby では,最初の代入式が現れる箇所以降でそのローカル変数が定義済みとみなされる。
「んなこと,たりめーじゃねーか」だって?
ところが,必ずしも当たり前じゃないんだな。次のスクリプトを見てくれ。
if false
x = 3
end
puts x #=> nil
x = 3
は決して評価されない。にも関わらず puts x
は NameError を出さない。
というのは,if 文のところで x
に代入する代入式が現れるので,この時点で x
という識別子は Ruby の処理系にとって〈見知らぬ者〉ではなくなるらしい。定義済みローカル変数になるのだ。
puts x
を実行するとき,x
は定義済みだ。しかし代入はされていない。こういうとき x
は値 nil を持つ。そういう仕様だ。
〈if 修飾子〉ってやつを使って
x = 3 if false
puts x #=> nil
と書いても同じこと。
再び x || (x = 1)
この章には誤りがあります。あとで調べて書き直します。
ではもう一度
x || (x = 1)
を見てみよう。
Ruby の処理系はこいつの言わんとするところをまず解釈し,そしておもむろにまず左側の x
を評価しようとする。
筆者も実は理解が怪しいのだが,Ruby の処理系はこの式を解釈するときに x = 1
を見ているので,x
を評価する際にこいつがローカル変数だということを知っているようなのだ。 この箇所が誤りです。あとで書き直します。
だから NameError は出さず,x
を評価することができる。結果は nil だ。
nil は偽なので,次に x = 1
を評価する。かくして x
に 1
が代入されることになる。
実はもっと単純な
x = x
でも同様のことが確かめられる。
こいつは x
が未定義でも NameError にはならない。未定義の場合は x
の値は nil になる。
どうも,この代入式を解釈するときに,識別子 x
が代入の左辺にあることからローカル変数と認識されるようだ。そして代入の右辺を評価すると nil になるので,x
に nil が代入される,ということらしい。
自己代入
さて,この記事のタイトルにも掲げた
x ||= 1
という書き方に戻ろう。
こういうやつは「自己代入」と呼ばれている。
自己代入にはいろいろな仲間がいるけど,いずれも
式1 演算子= 式2
という形をしている。
この〈演算子〉の部分には
+ - * / % ** & | ^ << >> && ||
が入る。これらは二つのグループに分かれる。
+
から >>
までは
式1 = 式1 演算子 式2
と解釈される。よって,たとえば
x += 1
は
x = x + 1
と解釈され,x
の値を 1 増やすことになる。
残りは &&
と ||
で,これらは
式1 演算子 (式1 = 式2)
と解釈される。
だから,
x ||= 1
は
x || (x = 1)
になるわけだ。
x || (x = 1)
と x = x || 1
は同じ?
&&
と ||
の場合だけ自己代入の定義が違うのが気になるね。
以下の二つを比較してみよう。
x || (x = 1)
x = x || 1
どちらの場合も,x
が未定義または〈偽〉のときだけ代入が行われ,x
が〈真〉のときは値は変わらない。式全体の値も常に一致する。
違いと言えば,x
が〈真〉のとき,前者は代入が行われないが,後者は行われる(自分自身の値が代入される)こと。
んー,だけど,自分の値が自分に入るんじゃ,代入しないのと同じなんでは? 結局,違いなんて無いんじゃねーの?
しかし,次の例はどうだろう。
h = Hash.new("default")
h[:foo] = h[:foo] || "foo"
h[:bar] || (h[:bar] = "bar")
p h #=> {:foo=>"default"}
まず,思い出してほしいんだけど,Ruby のハッシュのデフォルト値というのは,知らないキーで値を参照した場合に返す値のことだ。
さて,上のスクリプトで,式1 = 式1 || 式2
の形だと,式1
が〈真〉であっても代入は行われ,ハッシュにキーが追加されることになる。
しかし,式1 || (式1 = 式2)
の形だと 式1
が〈真〉の場合には代入が行われないので,ハッシュは変化しない。
だから,デフォルト値を持っているハッシュで,知らないキーを使って
some_hash_with_default[:unknown_key] ||= 1
とやっても,代入は行われず,ハッシュは変化しない。
これ,ちょっとした落とし穴だよね。
同じようなことは,オブジェクトの〈属性〉の参照/代入についても言える。
class C
def hoge
@hoge || "知らん"
end
def hoge=(value)
@hoge=value
end
end
c=C.new
c.hoge ||= "知っとる"
p c.hoge #=> "知らん"
インスタンス変数 @hoge
が nil でも c.hoge
が値を返してしまうため,代入は行われないわけだ。