質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

Q&A

解決済

2回答

414閲覧

Rubyの a = [1]; a[0] += a[0] += 1 という式が 3 になるのはなぜですか?

退会済みユーザー

退会済みユーザー

総合スコア0

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

0グッド

0クリップ

投稿2020/10/16 22:20

インタプリタを作っていて、Rubyの言語仕様を参考にしています。
以下のRubyのコードなんですが

ruby

1a = [1] 2a[0] += a[0] += 1

Rubyではa[0]の結果が3になります。
この仕様はプログラミング言語全般で、一般的な仕様なんでしょうか?
私が今作っている言語は結果が4になるのですが、これは間違っていると言えますか?

文脈的な解釈としては3も4もどちらも納得できると思うのですが。
結果を3にしたほうがメリットが多いのであれば、そちらの仕様を検討したいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2020/10/16 22:57 編集

>私が今作っている言語は結果が4になるのですが その言語名とコードを提示してください。 >結果を3にしたほうがメリットが多いのであれば それは要件次第です。ロジックも何かを実現するための手段の1つに過ぎません。 欲しい結果が違うならロジックを変更するまでです。書いたとおりにしか動かないので・
退会済みユーザー

退会済みユーザー

2020/10/16 23:06

なぜ言語名とコードが必要なんですか? Rubyの仕様について聞いています。
m.ts10806

2020/10/16 23:20 編集

でしたら「この仕様はプログラミング言語全般で、一般的な仕様なんでしょうか?」「私が今作っている言語は結果が4になるのですが」というのはなぜ書いたのでしょうか。 実際に比較対象があるのならそこは提示して仕様を比較すべきですし、 気にならない人のほうが少ないと思うのですけど。 先のコメントに書いた通り「書いたとおりにしか動かない」ので、出ている結果が全てです。 どこまで突っ込んで仕様を知りたいのかはこの質問内容からは読み取れませんでした。 質問は編集できますので具体的に記載してください。 現状では何が問題なのかが不明です。
Zuishin

2020/10/16 23:23

a[0] が元々 2 なら大抵の言語でそうなると思いますが、配列の概念と演算子とわからないのはどちらですか? この式が何をしているのか自分で説明してみてください。そうしたら回答者はその間違っている部分を正しく把握することができます。
hentaiman

2020/10/16 23:24

配列のindexが0から始まるか1から始まるかどっちがいいか?みたいな質問なので、どちらを取るか(メリットがあるか)を考えられるのは唯一思想を持っている質問者しかいない気が。
Zuishin

2020/10/16 23:28

> 私が今作っている言語は結果が4になるのですが、これは間違っていると言えますか? a[0] が元々 3 ならたいていの言語でそうなると思いますが、この質問のコードだけなら未初期化の変数の値を演算しているので、コンパイルエラーになったり、実行時エラーになったり、デフォルト値を用いたりと、言語によってもコンテクストによっても結果は様々です。
maisumakun

2020/10/16 23:35

> この質問のコードだけなら未初期化の変数の値を演算しているので えっと、どこがですか?
_._

2020/10/16 23:44

> a = [1] この時点の「a[0]」は「1」 > a[0] += a[0] += 1 は「(a[0] += a[0]) += 1」 丸カッコ内の「a[0] += a[0]」は「a[0] = a[0] + a[0]」 「a[0] = a[0] + a[0]」は「a[0] = 1 + 1」だから この時点の「a[0]」は「2」 その「a[0]」に「+= 1」すると「a[0]」は「3」になる …そういう話とは違いますか?「4」になるのはどういう流れですか?
maisumakun

2020/10/16 23:50

> a[0] += a[0] += 1 は「(a[0] += a[0]) += 1」 言語によっては代入は右結合です。 a[0] += (a[0] += 1) と解釈されます。
_._

2020/10/16 23:57

すみません丸括弧は a[0] += (a[0] += 1) ですね で丸括弧内で「a[0]」が「2」になっているから 左端の「a[0]」も「2」になっているはずで だから結果は「4」ということですか?
_._

2020/10/17 00:02

javascriptだと明示的にカッコを付けても「3」になりました a = [1] a[0] += (a[0] += 1) a[0] //3
Zuishin

2020/10/17 00:05

> えっと、どこがですか? a[1] = 1 のように読んでしまいました。 すみません、読み間違いです。
退会済みユーザー

退会済みユーザー

2020/10/17 00:19

コメントありがとうございます。 C++とRubyでは文脈の解釈が違うんですね。
Zuishin

2020/10/17 00:27 編集

C# では 3 になります。 PowerShell でも 3 になりました。 読みづらいだけなので、私としてはコンパイルエラーにしてほしいですね。
guest

回答2

0

Cだとこのあたりは規格としては未既定ですね。
gccでやると古いバージョンだと、配列要素と単純変数で違いが出ます。

C

1#include <stdio.h> 2 3int main(){ 4 int a[1]; 5 int b; 6 a[0]=1; 7 b=1; 8 9 printf("%d\n",a[0]+=a[0]+=1); 10 printf("%d\n",b+=b+=1); 11}

plain

1gcc 10.1.0 24 34 4 5gcc 4.9.4 64 74 8 9gcc 4.4.7 103 114

いずれもオプションはデフォルト。4.4.7でも-Oを付けると、4 4に。
また、-Wallを付けるとsequence-pointの警告が出ます。
warning: operation on 'a[0]' may be undefined [-Wsequence-point]等。

どっちでも良いんじゃ無いでしょうか。あるいは、エラーにするのもありでしょう。

似た問題で、f()+g()や、h(f(),g())で、fgのどっちが先にコールされるかとか。

投稿2020/10/17 00:29

otn

総合スコア84712

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/10/17 01:19

回答ありがとうございます。 おっしゃる通りどちらでもいいみたいなので、持ち帰って検討します。
guest

0

ベストアンサー

Rubyにおいてa[0]といった表現はいわゆる左辺値にはなりません。そしてa[0]とa[0] = x`は実際はメソッドであり、呼ばれるメソッドが異なります。Arrayの実装がCの配列と同じような動作になるようになっているだけで、厳密な動作が異なるので違いが出ます。この動作は、他の言語(特にCライク言語)とはかなり異なります。

Rubyでの動作は次のようになります。

Ruby

1a[0] += a[0] += 1 2# 自己代入演算子は右結合 3a[0] += (a[0] += 1) 4# 式1 op= 式2 は 式1 = 式1 op 式2 (||と&&の場合を除く) 5a[0] = a[0] + (a[0] = a[0] + 1) 6# []=も[]もただのメソッド 7a[]=(0, a[](0) + (a[]=(0, a[](0) + 1)) 8# +もただのメソッド 9a[]=(0, a[](0).+(a[]=(0, a[](0).+(1))) 10# メソッドとわかるように次のような別名をつけて、表現する。 11# (Rubyでこのような別名が標準であるわけではない) 12# []=(x,y) set(x,y) 13# [](x) get(x) 14# +(x) add(x) 15a.set(0, a.get(0).add(a.set(0, a.get(0).add(1)))) 16# 6 1 5 4 2 3 17# メソッドチェーンは左から順番に評価される。 18# メソッドの引数は左から順番に評価される。 19# 上は変数やリテラルといった評価がそれ以上できないところを除いての順番 20a.set(0, 1.add(a.set(0, a.get(0).add(1)))) # a: [0] 21a.set(0, 1.add(a.set(0, 1.add(1)))) # a: [0] 22a.set(0, 1.add(a.set(0, 2))) # a: [0] 23# []=は値をセットした後にその値を返す 24a.set(0, 1.add(2)) # a: [2] 25a.set(0, 3) # a: [2] 263 # a: [3]

しかし、例えば、C++ではa[0]は左辺値として解釈されます。そして、左辺の評価は一度のみです。
※C++は専門じゃないので、間違いがあるかも知れません。

C++

1a[0] += a[0] += 1; 2a[0] += (a[0] += 1); 3# [0]の左辺値表現(つまり、メモリ上の実際の場所)<a0>とします。 4# <a0>の値は現在は1です。 5# そして、+=は左辺を1度だけしか評価しません。 6<a0> += (a[0] += 1); # <a0>: 1 7<a0> += (<a0> += 1); # <a0>: 1 8# まずは、右辺を考えます。 9# <a0>の値は現在1なので、右辺としか解釈されるときは1となります。 10# それに1を足した物が、<a0>にセットされます。 11# そして、その戻り値も左辺値です。 12<a0> += <a0>; # <a0>: 2 13# <a0>は右辺として解釈されるときは2になっています。 14# それら二つをたすと4で、<a0>にセットされます。 15<a0>; # <a0>: 4 16# 右辺として式の値を決定すると。 174;

なお、+=はオーバーロードできますので、a[0]に整数ではない何かが入っている場合は、
上記の限りではないことに注意してください。

最後に、JavaScriptを見てみましょう。

JavaScriptでほa[0]は左辺値(参照)と解釈されます。しかし、右辺での右辺値(値)を求める前に、左辺の右辺値(値)を取得しています。

引用: 12.15.4 Runtime Semantics: Evaluation

  1. Let lref be the result of evaluating LeftHandSideExpression.
  2. Let lval be ? GetValue(lref).
  3. Let rref be the result of evaluating AssignmentExpression.
  4. Let rval be ? GetValue(rref).
  5. Let assignmentOpText be the source text matched by AssignmentOperator.
  6. Let opText be the sequence of Unicode code points associated with assignmentOpText in the following table: 【テーブルは略】
  7. Let r be ApplyStringOrNumericBinaryOperator(lval, opText, rval).
  8. Perform ? PutValue(lref, r).
  9. Return r.

上の2.の動作で左辺値の右辺値としての値を取得しています。その後に右辺を評価しています。つまり、右辺の評価によってlrefが示す値が変更されても、すでにlvalは取得済みのため、その後に変更されることはありません。


細かいところはさておき、上のような違いが出るのは、変数が箱なのか名札なのかのという違いだと思います。C/C++は箱ですが、RubyやJavaScriptはオブジェクトにに対する名札です。ただ、Rubyは実際はメソッドであるという多くの言語全体を見ると稀な作りなので、純粋オブジェクト指向言語を目指すと言うことがなければ、参考にしない方がいいでしょう。むしろ、かつてのPythonのように代入文しか認めない、つまり、自己代入(復号代入)も文なので、そういう書き方はできないとしたほうが良いかもしれません。(Python 3.8で、独裁者を追い出し、代入式を得ましたが…)

投稿2020/10/17 03:04

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問