Rubyにおいて,「||=」演算子を使うことの意義は分かるし,自分でもよく使うのですが…
他人のコードを見たRuby初心者の多くは,この風変わりなイディオムに困惑する.
a ||= []これは,aの値が空の配列になるかもしれないという意味だ.||=は以下を省略した構文である.
a = a || []このコードを理解するには,このOR演算子(||)について詳しく理解する必要がある.||演算子は,被演算子のいずれかがtrueであれば,trueを返す.
nilとfalse以外の値はすべてtrueになることを覚えておいてほしい.最初の被演算子がtrueなら,||演算子はtrueを返す.falseなら,2番目の被演算子を返す.これはつまり,被演算子がどちらもfalseでない限り,結果はtrueになるということだ.これは,いわゆるOR演算子の考えと同じだ.
ということは,先ほどのコードは以下のコードと同じだと考えられる.if a != nil a = a else a = [] endこのコードは以下のように翻訳できる.「もしaがnilでないならば,そのままにする.nilならば,空の配列にする.」熟練Rubyプログラマは,ifよりも||演算子のほうが優雅で読みやすいと考えるようだ.このイディオムは配列以外にも使える.変数がnilにならないようにするガード(見張り役)なので,このイディオムはnilガードと呼ばれる.
nilガードはインスタンス変数を初期化するのにもよく使われる.(略)
(メタプログラミングRuby*1, p.268)
上の説明,間違いが多数,目について困ります.
- 「a ||= []」*2は「a = a || []」ではなく,「a || (a = [])」です.リファレンスマニュアルの「演算子式」で確認できます*3.
- 「1 || true」はtrueではなく1と評価されます*4.これは,「||演算子は,被演算子のいずれかがtrueであれば,trueを返す」と「被演算子がどちらもfalseでない限り,結果はtrueになる」に対する反例になります.
- 「nilとfalse以外の値はすべてtrueになる」って,そんなことはありません.ここの「true」は「真」なのでしょう.全体的に,「定数true」と「真となる値」の区別がついていない印象があります.
- ifを使った代替コードの「if a != nil」は「if a != nil && a != false」または「if a」とし,次の行の「a = a」は単に「a」とすべきです.
言語仕様として,「a ||= []」は「a = a || []」ではなく「a || (a = [])」となっているのでそこまでなのですが,確認のコードを書いてみました.
#!/usr/bin/env ruby # -*- coding: utf-8 -*- # sample.rb class Sample def initialize @x = nil end def x puts "インスタンス変数@xを参照しました" @x end def x=(value) puts "インスタンス変数@xに#{value}を代入しました" @x = value end end if __FILE__ == $0 s = Sample.new puts "s.x ||= [] 1回目" s.x ||= [] puts "s.x ||= [] 2回目" s.x ||= [] puts "s.x = s.x だと?" s.x = s.x end
「変数aの値を参照する」ことや「変数aに代入を行う」ことを検出するような,Rubyのコードというのは,ちょっと難しそうです.また,=と||は再定義できない演算子である点にも,留意しないといけません.ですが,独自に定義したクラスのインスタンスsに対して,「s.xの値を参照する」ことや「s.xに代入を行う」ことはメソッドとして記述ができ,その中にputによる出力処理を書いておけば,参照・代入がどのタイミングでなされるか,目に見えて分かるという次第です.
上のプログラムで,実行前に考えることは次の2つです.
「s.x ||= []」が「s.x = s.x || []」と等価であれば,
s.x ||= [] 1回目 インスタンス変数@xを参照しました インスタンス変数@xに[]を代入しました s.x ||= [] 2回目 インスタンス変数@xを参照しました インスタンス変数@xに[]を代入しました s.x = s.x だと? インスタンス変数@xを参照しました インスタンス変数@xに[]を代入しました
という出力になります.
そうではなく,「s.x ||= []」が「s.x || (s.x = [])」と解釈されるのなら,
s.x ||= [] 1回目 インスタンス変数@xを参照しました インスタンス変数@xに[]を代入しました s.x ||= [] 2回目 インスタンス変数@xを参照しました s.x = s.x だと? インスタンス変数@xを参照しました インスタンス変数@xに[]を代入しました
です.実際に,いくつかの環境(1.8,1.9)で実行してみると,出力はすべて後者でした.
*1:http://ascii.asciimw.jp/books/books/detail/978-4-04-868715-7.shtml
*2:はてなダイアリー記法と衝突するので,地の文では,ブラケット記号をマルチバイト文字で表現しています.
*3:http://doc.okkez.net/static/192/doc/spec=2foperator.html, http://doc.okkez.net/static/187/doc/spec=2foperator.html, http://www.ruby-lang.org/ja/man/html/_B1E9BBBBBBD2BCB0.html
*4:「2||1」は,Rubyでは2で,Cでは1です.Cの場合は,論理OR演算子の評価結果は0か1のいずれかになるからです.「2|1」は,RubyでもCでも3です.ともに|は,ビットOR演算子だからです.