【2024年4月1日のTypeScriptニュース】次期バージョンでオブジェクト型に追加される新機能の紹介

本日、2024年**4月1日**は、TypeScriptの歴史に残る記念すべき日となりそうです。TypeScriptの次期バージョン**5.5.555**に実装予定の新機能の概要が公開されたのです。

この記事では、本日投稿された以下のissueの内容をかみ砕いて解説します。

https://github.com/microsoft/TypeScript/issues/58019

## Arranged Field Definitionの概要とモチベーション

issueのタイトルにもあるように、今回公開された新機能は**Arranged Field Definition** (**AFD**) と題されています。ここでのarrangedという単語は、「順序に意味がある」という意味に解するのがよいでしょう。つまり、オブジェクト型のフィールド定義において順序まで考慮されるということです。

TypeScriptはJavaScriptに型をつけた言語ですが、JavaScriptではオブジェクトの**プロパティに順序がある**ことが知られています。つまり`{ x: 1, y: 2 }`と`{ y: 2, x: 1 }`は意味的に異なるオブジェクトだということです。実際、この差は`Object.keys`などを使うことで観測可能です。`Object.keys`は与えられたオブジェクトのプロパティ名の配列を返しますが、その順序はプロパティが作成された順番です（例外もありますが割愛）。

しかし、これまでTypeScriptではオブジェクトのプロパティの順番を考慮する機能はありませんでした。つまり、プロパティの順番が違ってもオブジェクト型に代入することができてしまうのです。

```ts
type GoodObj = { x: number; y: number };

const bad = { y: 2, x: 1 };

// プロパティの順番が違うのに代入できてしまう！
const good: GooOobj = bad;
```

上の例だとそこまで問題が無いように思ってしまうかもしれません。TypeScriptチームもこれまでそう思っていたからこそ、TypeScriptでもプロパティの順番を考慮する機能がこれまで実装されなかったのでしょう。

issueでは、この考えが変わるきっかけとなったコードが紹介されています。

```js
function isGoodObject(obj) {
    return Object.keys(obj).reduce((last, next) => last === null ? last : last < next ? next : null, "") !== null;
}

> isGoodObject({ x: 1, y: 2 })
< true

> isGoodObject({ y: 2, x: 1 })
< false

> isGoodObject({ a: "hello", b: "world" })
< true

> isGoodObject({ b: "world", a: "hello" })
< false
```

このコードでは、与えられたオブジェクトが*good*かどうか判定しているのですが、なんとプロパティの順番によって結果が変わってしまいます。このコードでは、オブジェクトのプロパティがキーでソートされていることがチェックされています。

issueでは上のコードに出会ったときに驚きが次のように説明されています。

> It was a brilliant "Ah-ha!" moment for everyone on the team. I think Anders even spilled his coffee. Someone in JS was enforcing the arrangement of property keys in objects - in this case, that they be sorted. This was uncharted territory for us, and we immediately set to work on building a type representation for it.
>
> TypeScript needed a way for library authors to enforce key ordering. If you accept `{ x: 1, y: 2 }` but not `{ y: 2, x: 1 }`, TypeScript should have a type for that. No typechecker for JavaScript is complete unless it can represent this pattern.
>
> **（翻訳）** このコードの意味に気づいたときは、TypeScriptチーム全員にとって "Ah-ha!" となる[訳注: 目からうろこが落ちるような]瞬間でした。Anders[訳注: TypeScriptのリードアーキテクト。複雑・斬新なTypeScriptの機能はだいたいこの人が実装している]でさえコーヒーをこぼしていたと思います。JavaScriptにおいては、オブジェクトのプロパティの並び順に制限を加える人もいるのです――このケースでは、プロパティがソートされていることです。プロパティの並び順は我々にとって未踏の領域でした。我々は即座に、型システム上でプロパティの並び順を表現する機能の開発に着手しました。
>
> TypeScriptには、ライブラリの作者がキーの並び順を強制できる機能が必要です。`{ x: 1, y: 2 }`は受け入れられるが`{ y: 2, x: 1 }` は受け入れられないということを型で表現できなければなりません。いかなるJavaScript向け型チェッカーも、この設計パターンを表現できないうちはまだ未完成です。

ご存じのように、与えられた値が決められた制約を満たしているか判断することは、プログラムを予期せぬ挙動から守る上で重要です。そして、制約のチェックはランタイムで行うのもいいですが、型チェックによって静的に検査できるならばそちらのほうがなお望ましいでしょう。プロパティの並び順をユーザーに強制するという斬新なアイデアとの出会いは、TypeScriptチームにプロパティの並び順を型で表現する機能の必要性を理解させるには十分だったようです。

## プロパティの並び順を型で表現する構文

ここからは、新機能の具体的な使い方を見てみます。ご存じのように、従来のTypeScriptで次のように宣言されたオブジェクト型は、プロパティの並び順が異なるオブジェクトを弾くことができません。

```ts
type GoodObj = { x: number; y: number };
```

TypeScriptは後方互換性を重視する言語ですから、残念ですが、いきなりこれがプロパティの並び順に厳密になるという変更はできません。そこで、並び順に厳密なオブジェクト型を定義する構文が用意されました。それは、`interface`を拡張した次のようなものです。

```ts
interface Ordered {
    a: string;
    b: string;
    c: string;
} in that order;
```

従来の`interface`宣言の末尾に `in that order` と書くことにより、このオブジェクト型に当てはめられるオブジェクトはプロパティの順番が一致している必要があります。

```ts
interface OrderedVector {
    x: number;
    y: number;
    z: number;
} in that order;

declare function check(x: OrderedVector): void;

// OK
check({ x: 0, y: 0, z: 0 });

// 型エラー
check({ y: 0, x: 0, z: 0 });
```

分かりやすいですね。このような制限を加えることでコードの読みやすさが大きく向上することは言うまでもありません。

### 採用されなかった他の構文

最終的に上記の構文が採用されましたが、issueでは他の案も紹介されています。それらも面白かったのでここで紹介します。

1つ目の案は次のようなものです。

```ts
tyqe Ordered = { a: string, b: string }
```

このように、オブジェクト型を宣言するときに`type`ではなく`tyqe`キーワードを使うことで、プロパティの順番の強制が有効化されるというものです。

初見では既存の`type`と紛らわしいから良くないのではないかと思ってしまいますが、今どきのエディタではリガチャを使って`type`と`tyqe`の見た目を変えることができるので問題ではないと説明されています。

確かにそうなのでこの`tyqe`案で良さそうにも思うのですが、思わぬ問題がありました。それは、**複数のECMAScriptのプロポーザルで`tyqe`キーワードの使用が検討されていた**ことです。プロポーザルとは、JavaScriptに対する機能追加の案です。

もし`tyqe`がJavaScriptに入れば、TypeScriptとJavaScriptで`tyqe`の意味がコンフリクトしてしまう可能性があります。“JavaScript + 型”であることを原則とするTypeScriptとしてはこのような事態は何としても避けたいところです。

実際、他のプロポーザルでは`module`というキーワードを使うことも検討されており、仕様次第ではTypeScriptに古くから存在した`module`（現在は`namespace`が推奨されています）とのコンフリクトが発生してしまう恐れがあります。このような問題の種をこれ以上増やすのは得策ではありません。

もう一つの案は、次のように`then`キーワードを使う案です。

```ts
interface Ordered {
    a: string; then
    b: string; then
    c: string;
}
```

採用された案と比べると、オブジェクト全体だけでなく、一部のキーに対してのみ制約がかかることを表現できるなど、柔軟性があるのが魅力です。この案は結構感触が良かったようで、issue上で利点も複数挙げられています。

- `then`というキーワードは、**TypeScriptの主要なユーザー層であるPascalプログラマ**によってなじみ深いものである。
- 表現できる制約が増えて機能が複雑化することでTypeScriptユーザーの満足度が向上し、TypeScriptチームもジョブセキュリティが確保される。
- **`then`というプロパティを持つJavaScriptオブジェクトが使われることはめったにない**ため、後方互換性に優れる。

最後の点について補足すると、実はこのように `then` を使うのは既存のTypeScriptコードでも文法上は許されています。`noImplicitAny`が無効な状態では、このように`then`とだけ書くのは`then: any;`と等価です。そのため、「何らかの`then`を持つオブジェクト」を表現したくて`then`と書いていた既存のコードがもしあったとすれば、破壊的変更となってしまいます。

とはいえ、`noImplicitAny`が無効なことが前提となる上に、issueにも書かれている通り`then`というプロパティが普段のTypeScriptコードに出てくることはめったに無いため、この点は問題ではなさそうです。

では、この案はなぜ不採用となったのでしょうか。次のように説明されています。

> Unfortunately, we hit another roadblock -- Bill Gates has a patent on `then` following a semicolon back from the Microsoft BASIC days, and no one could get in touch with him to see if he'd license it to us.
>
> 残念ながら、この案にも障害がありました。Microsoft BASIC[訳注: 1975年に登場したBASICの処理系]の時代から、セミコロンに続いて`then`が続くことに対する特許をビル・ゲイツ[訳注: マイクロソフトの共同創業者]が保有していたのです。ライセンス交渉のために彼と連絡をつけることはできませんでした。

良さそうな案ではありましたが、特許の問題により採用することはできなかったようです。残念ですね。ちなみに、調べたところアメリカにおける特許の有効期間は20年でした。

## 関連する機能

以上が次期バージョンである**TypeScript 5.5.555**で追加予定の新機能の紹介でした。最後に、これに関連する事柄をいくつか紹介します。

実は、`in that order` という構文を採用したことで、思わぬ利点がありました。それは、次のようにユニオン型もサポートされることです。

```ts
type HelloOrWorld = "hello" | "world" in that order;
```

このようにユニオン型に対して値の順番が強制される場合、ソースコード上にユニオン型の各要素が現れる順番を一致させる必要があります。

```ts
// OK
const hw1: HelloOrWorld = Math.random() > 0.5 ? "hello" : "world";

// エラー
const hw2: HelloOrWorld = Math.random() <= 0.5 ? "world" : "hello";
```

また、issueでは特に触れられていませんが、このようにユニオン型の`in that order`をサポートすることで、順番制限付きオブジェクト型と`keyof`の関係もサポートされることが予想できます。

```ts
interface OrderedVector {
    x: number;
    y: number;
    z: number;
} in that order;

// "x" | "y" | "z" in that order 型になる（予想）
type VectorKeys = keyof OrderedVector;
```

その他、AFDの追加に関連するいくつかのコンパイラオプションの追加が予告されています。具体的には`--noAFD`, `--noImplicitAFD`, `--strictAFD`, `--isolatedAFD`, `--noUncheckedExperimentalInThatOrder`です。どれも字面からおおよそ役割を想像できますが、`--isolatedAFD`だけちょっと意味が分かりませんでした。意味がわかるTypeScript識者の方はぜひコメントで教えてください。

## まとめ

この記事では、**TypeScript 5.5.555**で追加が予告された**Arranged Field Definitions**について最新情報を解説しました。

プロパティの並び順という、**これまでTypeScriptから欠けていた大きなピース**を埋めてくれるこの新機能はとても期待できます。実装を楽しみに待ちましょう。

この記事では端折った情報もいくつかありますので、より詳しく知りたい方は[issueの原文](https://github.com/microsoft/TypeScript/issues/58019)を見に行きましょう。

### FAQ

**Q.** 嘘をついていいのは午前中までという説もありますが？
**A.** アメリカはまだ午前中ですよ。