たけ(tk)さん,ありがとうございます。

まず,

> (1) 「式1 ||= 式2」は「式1 = 式1 || 式2」のシンタックスシュガーであ
> る。動作においては同等であるように設計されている。

なんですが,以下の例からしてこれはありえないと思います。

  h = Hash.new("default")
  h[:foo] = h[:foo] || "foo"
  h[:bar] ||= "bar"
  p h #=> {:foo=>"default"}

自己代入で書いたときは,代入は行われていないんです。

同様に,るりまにも書かれていますが,foo.bar が真を返すとき,

  obj.foo ||= true

とやっても代入は行われません。

  obj.foo = obj.foo || true

ならば foo= メソッドが呼ばれますが,obj.foo ||= true だと
呼ばれない。

こういう場合だけは違いが出る,と。
「無駄なことをやってもしんどいから最適化のために省きました」
じゃなくて,言語仕様としてそうなっているんだと思います。

ですので,

> (5) (3)(4)は内部的な最適化による動作の省略の話なので、文法的に
> は、(1)(2)のみの説明に留めるのが正しい。せいぜい(3)までの説明に
> するべき。従って、
> http://docs.ruby-lang.org/ja/2.2.0/doc/spec=2foperator.html#selfassign
> が書きすぎである。

は言えないと思います。

一方,式1 || (式1 = 式2) のほうなんですが,こちらも同じではないものの
その違いは 式1 が未定義の場合だけです。

なぜ

  x ||= 1

は OK で,

  x || (x = 1)

は NameError かですが,それぞれの構文木を眺めていてなんとなく分かった
気がしてきました。


Ruby の処理系は,スクリプトをいったん構文木というものに変換して,それ
から実行するんですよね。
で,x という識別子がローカル変数なのかメソッドなのかは,構文木ができ上
がった時点ではすでに決定されている,と。そこまでに代入の形で出現したこ
とがあればローカル変数だし,そうでなければメソッド名とみなす,と。

※未定義の x を参照したとき,エラー名は undefined local variable or
method で,「ローカル変数かメソッドか知らん」と言っていますが,実は構
文木上ではメソッドだと決めつけられています。

この辺の動作が知りたくて,ruby_parser という gem を使ってみました。
構文木を得るには組み込みの ripper とかもあるんですが,ruby_parser の
ほうが見た目がよかったので。

  require "ruby_parser"
  RubyParser.new.parse("x") #=> s(:call, nil, :x)

この s( ) ってのが S 式とかいうものらしいです。
s(:call, nil, :x) は,x を call することを意味するみたいです。つまり
メソッドだと決めつけているんですね。
※真ん中の nil ってのは,レシーバーを指定してないってことでしょうか。


この方式で,まず x += 1 と x = x + 1 を調べると,同じになりました。

  s(:lasgn, :x, s(:call, s(:lvar, :x), :+, s(:lit, 1)))

:lvar はローカル変数の参照みたいです。
:lit はリテラル。
:lasgn は代入(割当=assign)

この場合はまさしくシンタックスシュガーと言っていいのでしょう。

しかし,foo.bar += 1 と foo.bar = foo.bar + 1 は違う結果になりました。
(結果は省略)


そして我らが x ||= 1 と x || (x = 1) ですが,

x ||= 1 の場合

  s(:op_asgn_or, s(:lvar, :x), s(:lasgn, :x, s(:lit, 1)))

x || (x = 1) の場合

  s(:or, s(:call, nil, :x), s(:lasgn, :x, s(:lit, 1)))

という結果に。ぜんぜん違います。後者はやはり x が未定義であるために
最初の x がメソッド呼出しと解釈されています。これでは NameError が
出るわけです。

この結果を眺めていて,自己代入のほうが :op_asgn_or という妙なものに
なっているのに気づきました。
要するに,||= って,構文木の上でもそういう特別なものなわけですね。


そうすると,x ||= 1 が NameError にならないのは,x = 1 がそうである
のと同じ理由なのかな,と思いました。

つまり,(x が未定義のときに)Ruby の処理系が

  x = 1

を見つけて,

  あ,イコールがついてるから代入だぞ。ここからは x はローカル変数だ

と考えるのと,

  x ||= 1

を見つけて

  あ,||= がついてるから自己代入だぞ。ここからは…(以下同じ)

と考えるのと同じである,という。




On 15/01/15 17:27, take_tk wrote:
> たけ(tk)です
> 
>>    式1 ||= 式2
>> 
>>    式1 = 式1 || 式2
>> でもなく・・・
> 
> (1) 「式1 ||= 式2」は「式1 = 式1 || 式2」のシンタックスシュガーであ
> る。動作においては同等であるように設計されている。
> 
> (2) 式1がローカル変数であり、かつ、未定義のとき、ローカル変数が代入
> 文の左辺に来た段階(「x =」を発見した段階で)nil で初期化し、その後に右
> 辺を評価する。従って、「x ||= 1」も「x = ( x || 1 )」も未定義エラーにな
> らない。
> 
> (2−2)式1がグローバル変数やインスタンス変数である場合には、未定義だ
> と nil の扱いになるので、未定義エラーにはならなずに、代入される。
> 
> (2−3)式1が定数であり、かつ、未定義のときには nil での初期化は行われ
> ないので、未定義エラーになる。
> 
> (3) 「x ||= 1」や「x = ( x || 1 )」で x が真であるとき、
> 「x = ( x || 1 )」では「x = x」という無駄な代入が実行されることになるが、
> 「x ||= 1」の場合は無駄な代入は省略され、何もしないように内部的に最適化
> されている。
> 
> (4)代入が行われないという意味では「x || ( x = 1)」に似ている。しかし、
> 最初の式1がローカル変数 x で未定義のとき、「x || ( x = 1)」では x が代入
> 文の左辺ではないので、未定義エラーになる。それに対して、「x ||= 1」も
> 「x = ( x || 1 )」も未定義エラーにならないので、、
> 「x ||= 1」が「x || ( x = 1)」と同等であるとは言えない。
> 
> (4−2)なお、グローバル変数やインスタンス変数の場合は、未定義であって
> も、「$x || ( $x = 1 )」はエラーにならない。
> 
> (5) (3)(4)は内部的な最適化による動作の省略の話なので、文法的に
> は、(1)(2)のみの説明に留めるのが正しい。せいぜい(3)までの説明に
> するべき。従って、
> http://docs.ruby-lang.org/ja/2.2.0/doc/spec=2foperator.html#selfassign
> が書きすぎである。
> 
> ということではないでしょうか?
> 
> ----
> 
> グローバル変数やローカル変数は未定義だと nil と評価される。
> 
> irb(main):001:0> x
> NameError: undefined local variable or method `x' for main:Object
> 
> irb(main):002:0> $x
> => nil
> 
> irb(main):003:0> X
> NameError: uninitialized constant X
> 
> 定数では左辺に来ても nil で初期化されない。
> 
> irb(main):004:0> x ||= 1
> => 1
> 
> irb(main):005:0> $x ||= 1
> => 1
> 
> irb(main):006:0> X ||= 1
> NameError: uninitialized constant X
> 
> グローバル変数やインスタンス変数なら「$x || ( $x = 1 )」はエラーにならな
> い。
> 
> irb(main):012:0> defined? $x
> => nil
> irb(main):013:0> $x || ( $x = 1 )
> => 1
> irb(main):014:0> defined? $x
> => "global-variable"
> irb(main):015:0>
> 
> irb(main):015:0> defined? @x
> => nil
> irb(main):016:0> @x || ( @x = 1 )
> => 1
> irb(main):017:0> defined? @x
> => "instance-variable"
> irb(main):018:0>
> 


-- 
5.5 / moji.gr.jp