このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

型パラメータと総称:どうしたらわかりやすい構文になるのかな

「CPS(継続渡し方式)変換をJavaScriptで説明してみるべ、ナーニ、たいしたことねーべよ」で、説明用の疑似構文として、JavaScriptに“型パラメータと総称”を入れたものを使ったのですが、型パラメータと総称の構文って、けっこうむずかしいですね。必ずしもJavaScriptに限らず、既存言語にもこだわらず、型パラメータと総称の構文について考えてみます。

いくつかの言葉

String, Integerなど型の名前(型定数)、X, Yのような不定な型を表す名前(型変数)、List, Pairなどの型構成子を含む記号表現をtype expressionというのだけど、直訳すると型式<かた・しき>。「型式」じゃ、みんな「けいしき」って読んじゃうだろうから、型表現にします。

型構成子総称型パラメータ付き型は、(区別する人もいるかもしれませんが)同義語です。与えられた型構成子(プリミティブな総称型)を使って型表現を作り、それに名前をつければユーザー定義の総称型になります。

型パラメータの可変引数

型Xと型Yのデータからなる対の型をPair<X, Y>と書くのはいいですよね。それじゃ、3つ組、4つ組に対しても、Triple<X, Y, Z>, Quadruple<X, Y, Z, W>, ... とか名前を付けてくの? 一般的なn-組をTupleとして、Tuple<X, Y>, Tuple<X, Y, Z>, Tuple<X, Y, Z, W> のようにTupleだけで書けたらいい。というわけで、型パラメータも可変引数が欲しいですね。

型構成子への値パラメータ

項目の型がXである配列の型は、Array<X>でいいですよね。んじゃ、サイズが5の配列をどう表現するか? Array<X, 5>でもいいけど、山形括弧(「<」と「>」)内には型パラメータだけを書きたい気もします。

値パラメータは丸括弧にして Array<X>(5)は? -- 型パラメータXを持つ総称関数の呼び出しと間違えそうだな。Array<X>[5]は? これなら配列の雰囲気が出てます。が、配列以外のときは雰囲気出ないかもね。

新しい総称型の宣言

可変引数のTupleがプリミティブとして準備されているとして、

  • Pair<X, Y> は、Tuple<X, Y> のこと
  • Attribute<V> は、Pair<String, V> のこと

のような宣言(定義)をどう書くべきでしょう。まー、次のような感じでしょう。

type Pair<X, Y> is Tuple<X, Y>;
type Attribute<V> is Pair<String, V>;

型パラメータをキーワードtypeに付けることも考えられます。

type<X, Y> Pair is Tuple<X, Y>;
type<V> Attribute is Pair<String, V>;

type<X, Y>と書いてあったら、直後に来る総称型(の名前)は型パラメータXとYを持つもんだ、と前もって知らせるスタイルだね。

型に対してラムダ記法を使えば:

type Pair is λ<X, Y>.Tuple<X, Y>;
type Attribute is λ<V>.Pair<String, V>;

λを大文字Λにするとか、他の記号がいいとかの議論はありますね。isとセミコロンの代替記号もいくらでもあるし。

総称型の再帰的な定義

型定数Nilと基本型構成子Cons(Pairと事実上同じ)が与えられていると、総称型List<X>は次のように定義できます。

type List<X> is Nil 
             or Cons<X, List<X>>;

orを縦棒にしてラムダ記法を使うと:

type List is λ<X>.(Nil | Cons<X, List<X>>);

わかりにくいかな。人間にも処理系にも再帰であることを知らせるために、

typerec List is λ<X>.(Nil | Cons<X, List<X>>);

とかにしたほうがいいかも。

型パラメータの省略

AnyとかObjectとか、「値はなんでもいい」を意味する型があるとします。型パラメータが省略されたとき、なんでもいい型を補完することは既に行われているようです。例えば、ListはList<Object>、ArrayはArray<Any>のこと、というように。

しかし、型定数を0引数の総称型とみなす立場とは整合しません。例えば、IntegerをInteger<>と考えても何の不都合もありませんが、IntegerはInteger<Any>のことだとすると、Integer<String>とかの意味も規定しないといけません。

可変型引数の型構成子Tupleの場合、単にTupleと書いたら、任意のタプルを表すのが一番自然な気がします。しかし、Tuple = Tuple<Any> だとすると、長さ1のタプルに限定されます。Tuple = Tuple<> なら長さ0に限定されて、もっと不便。

型パラメータを省略した総称型の解釈は意外にむずかしいです。

総称関数の定義

何もしない関数idも、強く片付けするなら総称関数になります。

/* (1) */
function id<X>(a:X) is a;
/* (2) */
function<X> id(a:X) is a;
/* (3) */
function id is λ<X>.λ(a:X).a;

1番目が無難だけど、実際のプログラミング言語では2番がいいでしょう。なぜかというと、自動型推論があれば、呼び出し時には関数の型パラメータが不要なんだけど、1番の書き方だとそれがわかりにくいですからね。

3番はわかりにくいな、「λ」が関数抽象と総称抽象(?)の両方で使われているので。やっぱり、記号を変えたほうがいいみたいです。型の抽象に論理記号∀を使う例があったような気がしますけど、僕はそれ嫌い。

総称関数の値の型を宣言

function id<X>(a:X) is a; で総称関数idが定義できますが、id<X>の値の型がXであることは明示的には宣言されてません。型推論を使えば宣言は不要ですが、ちゃんと宣言したいときの構文はどうすべきでしょう。

/* (1) */
function X id<X>(a:X) is a;
/* (2) */
function id<X>(a:X):X is a;
/* (3) */
function id<X>(a:X) is a:X;

1番目は、CやJavaの流儀です。このままだと、関数引数の型宣言(a:X)と不釣り合いですが、引数も(X a)にすれば、お馴染みの感じになります。

2番目の構文はJavaScript 2.0で採用予定のもので、関数定義ヘッダの最後で戻り値型を宣言します。

3番目は、関数定義ボディである式全体に型宣言を付けるものですが、あんまりメリットはなさそうですね。(型付きラムダ式では、λa:X.M:Y と書いて、型プロファイルが X→Y であることを示すこともあったような気もします。)

まだ考えるべきことがある

他にもいろいろと問題点はあるのですが、僕自身が見当が付かなくて困っていることを紹介します。

総称関数proj1<X, Y>とproj2<X, Y>を次のように定義します。

function proj1<X, Y>(a:X, b:Y):X is a;
function proj2<X, Y>(a:X, b:Y):Y is b;

この調子でいくと、proj1<X, Y, Z>、proj2<X, Y, Z>, proj3<X, Y, Z>は、

function proj1<X, Y, Z>(a:X, b:Y, c:Z):X is a;
function proj2<X, Y, Z>(a:X, b:Y, c:Z):Y is b;
function proj3<X, Y, Z>(a:X, b:Y, c:Z):Z is c;

任意のnに対して、複数の総称関数 proj[1], proj[2], ..., proj[n] を一気に定義できる方法はないでしょうかね。テキスト処理マクロ(テンプレート展開)を使えばできるでしょうが、そういうんじゃなくて、ちゃんとセマンティクスを持つような構文として、です。