Hatena::ブログ(Diary)

新言語 Xtalを作る日記

2007-06-11 Xtalの多値

多値の仕様が段々カッキリしてきました。

昔の多値の仕様 00:55  昔の多値の仕様を含むブックマーク

以前の実装では、多値を返す関数呼び出しは、多重代入で受取らないと、他の値は全て切り捨てられていました。

関数呼び出しの際、「呼び出し側が必要とする戻り値の数」がこっそり渡されていて、それ以上の値は単に切り捨てる実装になっていたわけです。

foo: fun(){ return 0, 1, 2; }

{
  a: foo();
  a.p; //=> 0
}

{
  a, b: foo();
  a.p; //=> 0
  b.p; //=> 1
}

これは「多値を返すけど、最初の戻り値だけを使うケースが多い関数」では都合のいい仕様でした。

それ以外では特に都合のいいケースは無く、配列と多値をあえて混同したい場合は逆に不都合となることがわかりました。

0.9.5.0ぐらいからは、これを止めて次のようにしてます。

今の多値の仕様と、Rubyの多値の仕様の比較 00:55  今の多値の仕様と、Rubyの多値の仕様の比較を含むブックマーク

現在の実装では、「呼び出し側が必要とする戻り値の数」よりも関数が返す多値が多い場合、余った部分を配列に纏めるようになっています。

values: fun(){ return 0, 1, 2; }

// 多値を返すことと、配列を返すことは意味的には等しいので次のように定義できる
// ただし、多値の方が無駄な配列が生成されないので実行効率はよい
// values: fun(){ return [0, 1, 2]; }

{
  a: values();
  a.p; //=> [0, 1, 2]
}

{
  a, b: values();
  a.p; //=> 0
  b.p; //=> [1, 2]
}

{
  a, b, c: values();
  a.p; //=> 0
  b.p; //=> 1
  c.p; //=> 2
}

Rubyで書くと次のようになります

def values
  return 0, 1, 2

  # 配列で返しても同じ挙動となる
  # return [0, 1, 2]
end

a = values
p a #=> [0, 1, 2]

a, *b = values
p a #=> 0
p b #=> [1, 2]

a, b, c = values
p a #=> 0
p b #=> 1
p c #=> 2

Xtalは、多重代入とブロックパラメータへの代入の規則はまったく同じとなります。

foo: fiber{
  yield 0, 1, 2
 
  // 配列で返しても同じ
  // yield [0, 1, 2];
}

foo{ |a|
 a.p; //=> [0, 1, 2]
}

foo{ |a, b|
  a.p; //=> 0
  b.p; //=> [1, 2]
}

foo{ |a, b, c|
  a.p; //=> 0
  b.p; //=> 1
  c.p; //=> 2
}

Ruby*1は、多重代入とブロックパラメータへの代入は、細かく規則が変わり、混乱します*2

def foo
  yield 0, 1, 2
end

foo{ |a|
  # warning: multiple values for a block parameter (3 for 1)
  p a #=> [0, 1, 2]
}
# 怒られました。これは次のように書かなければならないようです。
foo{ |*a|
  p a #=> [0, 1, 2]
}
# しかし、多重代入の場合では、*a = values と書くと a は [[0, 1, 2]] となってしまうため、
# a = values と書く必要があります


# さらに、returnの場合では多値を返すことと、配列を返すことで挙動は変わりませんでしたが、
# yieldは配列を返すと挙動が変わります
def foo
  yield [0, 1, 2]
end

foo{ |*a|
  p a #=> [[0, 1, 2]]
}

# こんどは次が正解
foo{ |a|
  p a #=> [0, 1, 2]
}

*1:1.8.6で試しました

*2:私は