Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php on line 456
新言語 Xtalを作る日記
[go: Go Back, main page]

新言語 Xtalを作る日記

スクリプト言語Xtalのソースと簡単なリファレンス ご意見ご感想コメント自由にどうぞ。

2007-06-19

Xtalの多値3 12:57 このエントリーを含むブックマーク

http://www.rubyist.net/~matz/20070611.html#p05

Xtalの多値について。Xtalでは多値と配列では(効率以外は)同じ意味を持つようにしたのだそうだ。「じゃあ多値要らないじゃん」という印象も持つが、純粋に最適化手法としてとらえれば良いのだろう、

はい、多値は効率の問題が無ければ要りません。

ただ、多重代入で

a,b = [1,2,3]

a = 1

b = [2,3]

になるのは正直いかがなものかと思う。単純に切り落として

a = 1

b = 2

にした方が良いのではないかな。

a,b = [1,[2,3] ]

と区別が付かないし。たぶん、切り落とすことで情報を失うことを嫌ったのだろう。気持ちはわかる。

いえ、情報を切り落とすことは特に問題に思っておりません。実際昔の実装では切り捨てていました。

このような仕様となった理由は「ある観点で見た時の統一性を持たせるため」「ある仕様の絡みから区別してはいけないため」です。

ある観点

ある観点とは、「左辺の数が一つのときの挙動、複数のときの挙動に統一性があるか」です。

この観点で見たとき、もし「a,b = [1,2,3] が a=1, b=2」となるのであれば、「a = [1,2,3] は a=1」であるべきです。左辺が一つのときは纏められ、二つ以上のときは切り捨てる、と挙動が異なっていますから。

Rubyもこの観点で見たとき、やはり統一性がありません。

Xtalは「右辺の値の数が左辺より多い場合は、余った右辺の値を、左辺最後の変数配列として纏めて代入する」と定義し、この観点での統一性を持たせているわけです。

もちろん、Xtalのやり方も、他の観点で見た場合に統一性が欠けています。しかし、イテレータ周りがこの観点で見た仕様だと割と上手く行ったため、これを重視しています。

「a,b = [1,2,3]」と「a,b = [1, [2, 3] ]の区別

Xtalでは、次のように「多値の追加」が出来ます。

foo: fun(){ 
	// 5と6、多値を返す
	return 5, 6; 
}

bar: fun(){
    // 1と2と、fooで返す全ての値を返す
	return 1, 2, foo();
}

a, b, c, d: bar();

[a, b, c, d].p; //=> [1, 2, 5, 6]

この時、「多値と配列は同じ」という前提があるため、fooの実装が「fun(){ return [5, 6]; }」であっても、同じように動作しなければなりません。よって、[1, 2, 5, 6]と[1, 2, [5, 6] ]は同じである必要があり、多重代入の際「区別してはならない」のです。


この「区別できない」特性のため、「意図していない、配列と多値の混同が起きる場合」があります。

// この関数の戻り値は「多値のつもりではない配列」とする。
foo: fun(){
  return [10, 20];
}
bar: fun(){
  // 5とfoo()の戻り値を返す。
  return 5, foo();
}

a, b, c: bar();
[a, b, c].p;

これで、[5, 10, 20] と出力されますが、本当は [5, [10, 20], null]となることを望んでいた。のような場合ですね。

これは、barが三つの多値を返すかのように受取ったのが原因です。正確に「a,b = bar()」とすればa=5, b=[10, 20]」と代入されます。

関数仕様を決める人は「関数(またはイテレータが)がいくつ多値を返すかを正確に予測できる仕様」にする必要があります。

それさえされれば、特に問題は無いと考えています。

多値の追加の具体例

多値の追加がちゃんと活用されている例として、Iterator::with_indexがあります。

// with_indexの定義は次のような感じでされている
/*
Iterator::with_index: method(index: 0){
  return fiber{
    i: index;

    // 自身をイテレートする。
    // ブロックパラメータ部分を省略すると、
    // this{ |it| と書いたのと同じ意味となる。
    // ブロックパラメータは多重代入と同じ挙動で代入されるため、
    // もしブロックパラメータが多値の場合、配列に変換される。
    this{

      // インデックスと自身のイテレートで取得した要素をyieldする。
      yield i, it;
      i++;
    }
  }
}
*/

// 配列のeachは単値をブロックパラメータとして渡す
[1,2].each.with_index{ |index, value|
  %f(index=%s, value=%s)(index, value).p;
}
//=> index=0, value=1
//=> index=1, value=2

// 連想配列のeachはキーと値の多値をブロックパラメータとして渡す
// 連想配列.each.with_indexは、ブロックパラメータとして、
// 本来「index, [key, value]」を渡してくるが、
// ブロックパラメータを三つにすると、多重代入のルールにより
// それぞれに値が代入される。
["a":"ka", "i":"ki"].each.with_index{ |index, key, value|
  %f(index=%s, key=%s, value=%s)(index, key, value).p;
}
//=> index=0, key="a", value="ka"
//=> index=1, key="i", value="ki"

// ブロックパラメータを二つにすると、当然keyvalueには配列で代入される。
["a":"ka", "i":"ki"].each.with_index{ |index, keyvalue|
  %f(index=%s, keyvalue=%s)(index, keyvalue).p;
}

//=> index=0, keyvalue=["a","ka"]
//=> index=1, keyvalue=["i","ki"]

Xtalの多値と多重代入まとめ 12:57 このエントリーを含むブックマーク

  • 多値と配列は同じである
  • 多重代入のルールと、ブロックパラメータへの代入のルールは同じである
  • 右辺の多値の最後の要素が多値(配列)なら、それは展開される
  • 左辺の値の数が多い場合は、余った左辺をnullで埋る*1
  • 右辺の値の数が多い場合は、余った右辺の値を、左辺最後の変数配列として纏めて代入する
  • 右辺と左辺の値の数が同じ場合は、そのままそれぞれに代入される
  • 関数が何個の多値を返すのか、ブロックパラメータの個数が何個になるのかは正確に予測できなければならない

Xtalの昔の多値の説明と、何故それを止めたのかの理由 12:57 このエントリーを含むブックマーク

Xtalは昔は「多値と配列は違うもの。余った多値は完全に切り捨てる」という実装で、多値を配列に纏める記法 (a,*b = 1,2,3)」、「配列を多値に展開する記法 (a,b = ary.values)」が存在していました。

つまり、「a,b = 1,2,3」は「a=1, b=2」であり、「a = 1,2,3」は「a=1」、そして「*a = 1,2,3」は「a=[1,2,3]」でした。

実に統一性がある、と、一時は満足したのですが、実装した後冷静にこれで何が嬉しいのか考えてみました。これで嬉しい場面は「よく使う値と、ほとんど使わない値を同時に返す関数がある。その関数を使うときにほとんど使わない二つ目以降の値を気にしなくてもいい」ぐらいしかありません。

はて、そんなの「「よく使う値だけを返す関数」と「よく使う値とほとんど使わない値を多値で返す関数」と別々の関数として用意すれば利便性は同じではないのか」、と思いました。後者で前者は簡単に実装出来ます。

それより「a = foo()」で fooが多値を返そうが単値を返そうが気にせずに受取れて、それを「return a」または「yield a」で全部返せた方が便利ではないかと思いました。*2

もう一つ、仮想マシンの実装が簡潔になることもありました。他の仮想マシン*3に載せる時も、こっちの仕様の方がが楽で効率も良くなるんではないか、と予測し、この仕様を止め、今の仕様に変更しました。

*1:これはエラーにするかも。左辺の数が多すぎるということは、あきらかに多値の数の個数を正確に予測できていない

*2:昔のやり方だと、「*a = foo()」「return a.values」としなければならず、単値の時ムダに配列を生成する必要があり、その分効率も悪い

*3FlashVMだとか、JVMだとか