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