[ruby-list:50065] Re: x ||= 1
From:
"5.5" <5.5@...>
Date:
2015-01-17 05:05:08 UTC
List:
ruby-list #50065
5.5 です。
結論に達したわけではなく,「こうなんかな」と思たことを書いた
だけです。
たけ(tk)さんにご指摘いただいたように既に誤りも判明しましたし。
Ruby の構文解析器(っていうことでいいのかな?)を知っている人
にとっては自明のことなのでしょうが,parse.y(でいいのかな?)
を読む能力も元気もありません。
Ruby の振る舞いはよく練られたものなのでしょうから,これでいい
んじゃないでしょうか。
ドキュメントでは以下のようなことに注意すると良いんではないで
しょうか。
・自己代入は二つのグループに分かれる。
・自己代入の左辺に来る式によっては話がややこしい。
・安易にシンタックスシュガーで説明すると困ったことに。
+= だって,式1 += 式2 が 式1 = 式1 + 式2 と同じになるとは限り
ません。
以下のような単純なケースでも違いが出ます。foo が呼ばれる回数
が違います。
(中田さんが先に書かれたことと本質的に同じですが)
def foo
puts "foo called!"
obj=Object.new
def obj.bar
puts "bar referred!"
9
end
def obj.bar=(val)
puts "bar assigned!"
end
obj
end
foo.bar += 1
puts
foo.bar = foo.bar + 1
※こういう bar や bar= の呼出しを参照,代入と呼んでいいのかどうか
しりませんが。
On 15/01/16 5:49, Tanaka Kazuki wrote:
> 5.5@moji.gr.jpさん
>
> 興味深く読まさせて頂きました.
>
> 何か結論に達せられた様に見受けられますが、
>
> つまるところドキュメント(またはRubyの振る舞い)がどうあるべきだと思いま
> すか?
>
> それとも何か合点の行く解釈を見つけられたのでしょうか.
>
> 田中
> --
> ===============================
> Keio university mathematical sciences
> 田中 和希 Kazuki Tanaka
>
> mail: mail@tanakakazuki.com
> HP: http://gogotanaka.me/
> Blog: http://blog.gogotanaka.me/
> Twitter: @gogo_tanaka
> Facebook: 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
--
5.5@moji.gr.jp