DEV Community

SUZUKI Tetsuya
SUZUKI Tetsuya

Posted on

"type t" とは何か

※この記事は 2018年01月12日 に Qiita に投稿したものです。

※Reason 向けの内容ですが、 OCaml とも共通します。


Reason で書かれたコードを見ていると、ところどころで type t という一文字の型名を目にします。例えばこういうの:

module Synthetic = {
  type tag;
  type t = synthetic(tag);
  ...
}

これは 1モジュール1データ型主義 という OCaml の一慣習から来ています(いると思われます)。

Reason のドキュメントにレコード定義の一例があります。

type person = {age: int, name: string};
type monster = {age: int, hasTentacles: bool};

どちらのレコードも age という同名のフィールドを持ちます。 Reason のレコードは名前空間が独立しておらず共有されるため、型推論時の personmonster の区別はフィールド名の組み合わせで判断されます。レコードの生成時に name フィールドがあれば person 型、 hasTentacles フィールドがあれば monster 型と判断されます(もちろん明示的にレコード型を指定することもできます)。

/* person */
let me = {age:100, name:"me"};

/* monster */
let me = {age:100, hasTentacles:true};

では、まったく同じフィールドを持つレコード型があったらどうでしょうか。

type person = {age:int, name:string};
type monster = {age:int, name:string};

/* どっちのレコード? */
let me = {age:100, name:"me"};

特に型の指定がなければ、フィールド名では型を判断できません。このような場合、 Reason では最後に定義された型とされます。 memonster 型です。

ですので、次のように person を引数に受け取る関数に me を渡すと型エラーになります。

let me = {age: 100, name: "me"};

/* person 型の引数を取るだけで何もしない関数 */
let f = (who: person) => ();

f(me);

コンパイルエラー:

  13 │ let f = (who: person) => ();
  14 │ 
  15 │ f(me);

  This has type:
    monster
  But somewhere wanted:
    person  

memonster 型だと判断されました。この場合は me の型を person と明示的に指定すれば大丈夫です。

let me: person = {age: 100, name: "me"};

OCaml だとフィールド名が重複すればコンパイル時に警告されます。 Reason の唯一の工夫のように見えますが、かえって落とし穴ができてしまった気もします。

ただし、既存のレコードとフィールド名が重複していると警告 42 で注意されます。 (デフォルトの警告の設定では無効です)

   9 │ };
  10 │ 
  11 │ let me:person = {age: 100, name: "me"}; // ``age`` の部分がハイライトされる
  12 │ 
  13 │ let f = (who: person) => ();

  this use of age required disambiguation

ここで元の問題に戻ります。以上の通りレコードは名前空間にならないので、フィールド名が重複する複数のレコードを定義する場合は、フィールド名に適当な接頭辞をつければ問題は回避できます。でも person_agemonster_age といちいちやるのはあまりかっこよろしくない。だからそれぞれのレコード型に専用のモジュールを用意すればフィールド名の重複を気にかけなくていい:

module Person = {
  type t = {
    age: int,
    name: string
  };
};

module Monster = {
  type t = {
    age: int,
    name: string
  };
};

このときに使う型名が t です("type" の "t" かな?)。かつてのレコード personmonster はそれぞれ Person.tMonster.t として定義されます。 t とつく型は、そのモジュールの中心的なデータ型だとわかります。インターフェースファイルである .reit を探せば、 API のだいたいの意味が推測できます。

以上です。 Reason は OCaml を丸ごと新しい文法で覆っただけなので、 OCaml の不便な点も、不便を解消するためのイディオムもそのまま引きずっています。改善すべき点は文法以外にもあるんじゃないですかね...

Top comments (0)