From: "5.5" <5.5@...> Date: 2015-01-16T01:12:15+09:00 Subject: [ruby-list:50060] Re: x ||= 1 たけ(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