Ruby の自己代入 x ||= 1 の謎

**この記事にはいくつか誤りがあることが分かったので，全面的に書き直す予定です。ごめんなさい。**

この記事は Ruby 初心者向け。

Ruby で，`x` にマトモな値が入っていない場合だけ何かを代入したい，というときに

```rb
x ||= 1
```

とかって書くよね。「マトモな値」というのは，ここでは〈真〉と評価される値のことを指している。

ちなみに Ruby では nil と false 以外のすべてのオブジェクトが〈真〉と評価される。

# 解釈

この文は

```rb
x || (x = 1)
```

と解釈されることになっている。

読者の中には，「えっ？

```rb
x = x || 1
```

じゃないの？」と思う人もいるかもしれないけど，そうじゃない。その話はあとでするね。

# `||` という演算子

`||` という二項演算子は，OR 条件の論理演算を行う。

`式1 || 式2` を評価した結果は，`式1` と `式2` の少なくとも一方が〈真〉のときに〈真〉となり，`式1` と `式2` が両方とも〈偽〉のときに〈偽〉となる。

こんな疑問が湧かないかな？

> でもさ，評価結果が「〈真〉となる」とか「〈偽〉となる」とか言ったってさ，具体的には何になるのさ？

もっともだ。Ruby では式の値はすべて何らかのオブジェクトになるわけだから，どういうオブジェクトなんだよ？っていう疑問だね。

じゃあ，言い直そう。

`式1 || 式2` を評価した結果は，`式1` が〈真〉のときは `式1` の評価結果となり，`式1` が〈偽〉のときは `式2` の評価結果となる。

ちょっと待てよ，さっきと全然違うじゃねーか。

いや，言い方は違うけど，これが OR 条件の評価になっていることは，`式1` や `式2` に nil だの 3.14 だのを入れて真／偽の組合せをいろいろやってみれば分かる。

```rb
# 偽 OR 偽 → 偽 の例
puts nil || false #=> false

# 偽 OR 真 → 真 の例
puts false || :hoge #=> :hoge

# 真 OR 偽 → 真 の例
puts "" || nil #=> ""

# 真 OR 真 → 真 の例
puts [] || 3.14 #=> []
```

# 実行

では

```rb
x || (x = 1)
```

がどのように実行されるかを見てみよう。

`x` が〈偽〉と評価される値を持っている場合，`x = 1` が評価される。これによって `x` に `1` が代入されるわけだ。

この場合，式全体の評価値は `1` となる。なぜなら式 `x = 1` の評価値が `1` だからだ。あ，ちなみに Ruby では代入も〈式〉の一種で，値を持つんだからね。

一方，`x` が〈真〉と評価される値を持っている場合，`x = 1` は評価しない。そして式全体の評価値は `x` の評価値となる。

# `x` が未定義の場合

実は `x` に `1` が代入されるのは，`x` が偽のときだけではない。`x` が未定義の場合も代入が行われる。

だから，

```rb
x ||= 1
puts x
```

とだけ書いたスクリプトは，期待通り 1 を表示してくれる。

しかし，あなたはまたこんな疑問を持つかもしれない：

> `x` が未定義だったら `x || (x = 1)` を評価しようとして NameError を出すはずでは？

その疑問ももっともだ。その前に，NameError って何だったっけ。

# NameError

`x` が未定義のときに

```rb
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 では，最初の代入式が現れる箇所以降でそのローカル変数が定義済みとみなされる。

「んなこと，たりめーじゃねーか」だって？

ところが，必ずしも当たり前じゃないんだな。次のスクリプトを見てくれ。

```rb
if false
  x = 3
end
puts x #=> nil
```

`x = 3` は決して評価されない。にも関わらず `puts x` は NameError を出さない。

というのは，if 文のところで `x` に代入する代入式が現れるので，この時点で `x` という識別子は Ruby の処理系にとって〈見知らぬ者〉ではなくなるらしい。定義済みローカル変数になるのだ。

`puts x` を実行するとき，`x` は定義済みだ。しかし代入はされていない。こういうとき `x` は値 nil を持つ。そういう仕様だ。

〈if 修飾子〉ってやつを使って

```rb
x = 3 if false
puts x #=> nil
```

と書いても同じこと。


# 再び `x || (x = 1)`

**この章には誤りがあります。あとで調べて書き直します。**

ではもう一度

```rb
x || (x = 1)
```

を見てみよう。

Ruby の処理系はこいつの言わんとするところをまず解釈し，そしておもむろにまず左側の `x` を評価しようとする。

筆者も実は理解が怪しいのだが，Ruby の処理系はこの式を解釈するときに `x = 1` を見ているので，`x` を評価する際にこいつがローカル変数だということを知っているようなのだ。 **この箇所が誤りです。あとで書き直します。**

だから NameError は出さず，`x` を評価することができる。結果は nil だ。

nil は偽なので，次に `x = 1` を評価する。かくして `x` に `1` が代入されることになる。

実はもっと単純な

```rb
x = x
```

でも同様のことが確かめられる。

こいつは `x` が未定義でも NameError にはならない。未定義の場合は `x` の値は nil になる。

どうも，この代入式を解釈するときに，識別子 `x` が代入の左辺にあることからローカル変数と認識されるようだ。そして代入の右辺を評価すると nil になるので，`x` に nil が代入される，ということらしい。


# 自己代入

さて，この記事のタイトルにも掲げた

```rb
x ||= 1
```

という書き方に戻ろう。

こういうやつは「自己代入」と呼ばれている。

自己代入にはいろいろな仲間がいるけど，いずれも

```rb
式1 演算子= 式2
```

という形をしている。

この〈演算子〉の部分には

```
+ - * / % ** & | ^ << >> && ||
```

が入る。これらは二つのグループに分かれる。

`+` から `>>` までは

```rb
式1 = 式1 演算子 式2
```

と解釈される。よって，たとえば

```rb
x += 1
```

は

```rb
x = x + 1
```

と解釈され，`x` の値を 1 増やすことになる。

残りは `&&` と `||` で，これらは

```rb
式1 演算子 (式1 = 式2)
```

と解釈される。


だから，

```rb
x ||= 1
```

は

```rb
x || (x = 1)
```

になるわけだ。


# `x || (x = 1)` と `x = x || 1` は同じ？

`&&` と `||` の場合だけ自己代入の定義が違うのが気になるね。

以下の二つを比較してみよう。

```rb
x || (x = 1)
```

```rb
x = x || 1
```

どちらの場合も，`x` が未定義または〈偽〉のときだけ代入が行われ，`x` が〈真〉のときは値は変わらない。式全体の値も常に一致する。

違いと言えば，`x` が〈真〉のとき，前者は代入が行われないが，後者は行われる（自分自身の値が代入される）こと。

んー，だけど，自分の値が自分に入るんじゃ，代入しないのと同じなんでは？　結局，違いなんて無いんじゃねーの？

しかし，次の例はどうだろう。

```rb
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` が〈真〉の場合には代入が行われないので，ハッシュは変化しない。

だから，デフォルト値を持っているハッシュで，知らないキーを使って

```rb
some_hash_with_default[:unknown_key] ||= 1
```

とやっても，代入は行われず，ハッシュは変化しない。

これ，ちょっとした落とし穴だよね。

同じようなことは，オブジェクトの〈属性〉の参照／代入についても言える。

```rb
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` が値を返してしまうため，代入は行われないわけだ。