論理演算子の自己代入について、ほとんどのRubyテキストで誤った説明をしている

などと大上段に構えた私はまだRuby(ほぼ)一年生です。でも気になったのです。

a += b
a = a + b

なのは直ぐに分かります。
しかし、

a ||= b
a &&= b

a = (a || b)
a = (a && b)

ではなく

a || (a = b)
a && (a = b)

だと
Ruby 1.9.3 リファレンスマニュアル 演算子式
にあります。
でも、出たばかりの

であっても上の誤った説明を為しています。

なぜでしょう

論理演算子&&, ||は論理判断だけを行います。論理演算子の左側だけで評価が済んでしまった場合には右側を評価しません。しかし右側を評価した場合には右側の値を返します。ただし以下のような注意点があります。

■ 自己代入演算子
Rubyには+=など2項演算子と組み合わさってできている代入演算子がありますが、これらは
「自己代入演算子」と呼ばれていて、
var += val

var = var + val
というような形で元になっている演算子を呼び出します*31。
*31 ただし、varは一度しか評価されません
オブジェクト指向スクリプト言語Ruby 第2章 p.57 (pdf)

なので、今回の場合だと a が一度しか評価されない、わけですね*1

正しい説明で実行

a ||= b
a &&= b

a || (a = b)
a && (a = b)

として確認してみます。

  • a = false, nil の場合
a || (a = b) => (a = b) # a だけでは評価できないので a || (a = b) を評価する。
a && (a = b) => a(= false, nil) # a だけで評価。 (a = b) は評価なし。
  • a ≠ false, nil の場合
a || (a = b) => a(= false, nil) # a だけで評価。 (a = b) は評価なし。
a && (a = b) => (a = b) # a だけでは評価できないので a && (a = b) を評価する。

誤った説明で実行すると

a ||= b
a &&= b

a = (a || b)
a = (a && b)

としてみるとどうなるでしょう。

  • a = false, nil の場合
a || b => b # a だけでは評価できないので a || b を評価する、が a を再評価できない…
a && b => a(= false, nil) # a だけで評価。b は評価なし。
  • a ≠ false, nil の場合
a || b => a(≠ false, nil) # a だけで評価。b は評価なし。
a && b => b # a だけでは評価できないので a && b を評価する、が a を再評価できない…

余談(?)

本当に想像なのですが、a を一度しか評価しないのは、

 代入演算子で最後のポイントは、オブジェクト自身を自分に代入する場合を避けるこ
とです。第1に、ムダなことは避けるというのが常識的な理由です(常識的な判断は困
ったエラーを未然に防ぎます)。第2はコード上の問題を引き起こします。もし、コピ
ーを受けるオブジェクトのメモリ領域を再設定するようにしている場合、データの値が
代入される前にその領域を削除してしまうことになります。
行列ライブラリの設計(2)4 代入演算子、フレンド関数 3.4.4 代入演算子では自己代入を避ける

ということではないかなーと。


補足

プログラミング言語 Ruby

プログラミング言語 Ruby

では

実際には、自己代入演算子の||=は、今の説明*2とは少し異なる動作をする。||=の左辺がnilやfalseでなければ、代入は行われない。左辺が属性や配列要素なら、代入を実行するセッターメソッドは呼び出されないのである。
(4.5 代入 4.5.4 自己代入 p.101)

と記載されています。

より良い記事

http://d.hatena.ne.jp/kitokitoki/20111022/p4
こちらのほうが正確に記載されています。ご一読を。

*1:多分これが原因だと思うんですけど、間違ってたらすみません

*2:result ||= # => result = result ||