pick ADT

styxlibの話に入る前にpickについて書いておく。メッセージパッシングのプログラムでは、メッセージをあるフォーマットのデータ構造にpack/unpackする必要があるが、pickはそんなときに便利な機能だ。Cでは、構造体の先頭にメッセージの種類(or タグ)を格納しておいて、その後にunionを使って、種類ごとに異なるデータ構造が続く。LimboにはCのunionのようなpickという構文がある。以下では説明のために、一つのpickと関数からなるApick ADTを宣言する*1。Apickの各タグ(Stirng、Int、Real)は一つのメンバしかもっていないが、もちろん複数のメンバを持つことができる。

Apick : adt {
  pick {
    String => val: string;
    Int => val: int;
    Real => val: real;
  }
  printeval : fn(this : self ref Apick);
};

ここでpickへの書き込みは次の通り。

  r := ref Apick.Real;
  r.val = 3.14;

  s := ref Apick.String("Hello, World!");
# s := ref Apick.String; s.val = "Hello, World!"でもOK

関連して、LimboにはCにはないtagof演算子がある。これは与えられたリファレンスがpick ADTの何番目のタグか返す。上の例では"tagof s"は0、"tagof r"は2となる。tagofを使って、Apickリファレンスの型名を返す関数を定義してみる。

tag(t : ref Apick): string
{
  r: string;
  case tagof t {
    tagof Apick.String => r = "String";
    tagof Apick.Int => r = "Int";
    tagof Apick.Real => r = "Real";
  }
  return r;
}

最期にpick文を使った、printval関数を見てみよう。pick文はcaseに似ているが、ラベルとしてpickの要素を指定する。

Apick.printval(this: self ref Apick)
{
  pick t := this {
    String => sys->print ("%s: %s\n", tag(t), t.val);
    Int => sys->print ("%s: %d\n", tag(t), t.val);
    Real => sys->print ("%s: %f\n", tag(t), t.val);
  }
}

*1:説明のコードはInfernoのマニュアルから。