[ruby-list:50061] Re: x ||= 1
From:
Tanaka Kazuki <mail@...>
Date:
2015-01-15 20:49:23 UTC
List:
ruby-list #50061
5.5@moji.gr.jpさん
興味深く読まさせて頂きました.
何か結論に達せられた様に見受けられますが、
つまるところドキュメント(またはRubyの振る舞い)がどうあるべきだと思いますか?
それとも何か合点の行く解釈を見つけられたのでしょうか.
田中--===============================Keio university mathematical sciences 田中 和希 Kazuki Tanaka
mail: mail@tanakakazuki.comHP: http://gogotanaka.me/Blog: http://blog.gogotanaka.me/Twitter: @gogo_tanakaFacebook: https://www.facebook.com/gogogogotanaka===============================
> Date: Fri, 16 Jan 2015 01:12:15 +0900
> From: 5.5@moji.gr.jp
> To: ruby-list@ruby-lang.org
> 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