本日、2024年4月1日は、TypeScriptの歴史に残る記念すべき日となりそうです。TypeScriptの次期バージョン5.5.555に実装予定の新機能の概要が公開されたのです。
この記事では、本日投稿された以下のissueの内容をかみ砕いて解説します。
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ではオブジェクトのプロパティの順番を考慮する機能はありませんでした。つまり、プロパティの順番が違ってもオブジェクト型に代入することができてしまうのです。
type GoodObj = { x: number; y: number };
const bad = { y: 2, x: 1 };
// プロパティの順番が違うのに代入できてしまう!
const good: GooOobj = bad;
上の例だとそこまで問題が無いように思ってしまうかもしれません。TypeScriptチームもこれまでそう思っていたからこそ、TypeScriptでもプロパティの順番を考慮する機能がこれまで実装されなかったのでしょう。
issueでは、この考えが変わるきっかけとなったコードが紹介されています。
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で次のように宣言されたオブジェクト型は、プロパティの並び順が異なるオブジェクトを弾くことができません。
type GoodObj = { x: number; y: number };
TypeScriptは後方互換性を重視する言語ですから、残念ですが、いきなりこれがプロパティの並び順に厳密になるという変更はできません。そこで、並び順に厳密なオブジェクト型を定義する構文が用意されました。それは、interface
を拡張した次のようなものです。
interface Ordered {
a: string;
b: string;
c: string;
} in that order;
従来のinterface
宣言の末尾に in that order
と書くことにより、このオブジェクト型に当てはめられるオブジェクトはプロパティの順番が一致している必要があります。
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つ目の案は次のようなものです。
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
キーワードを使う案です。
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
という構文を採用したことで、思わぬ利点がありました。それは、次のようにユニオン型もサポートされることです。
type HelloOrWorld = "hello" | "world" in that order;
このようにユニオン型に対して値の順番が強制される場合、ソースコード上にユニオン型の各要素が現れる順番を一致させる必要があります。
// OK
const hw1: HelloOrWorld = Math.random() > 0.5 ? "hello" : "world";
// エラー
const hw2: HelloOrWorld = Math.random() <= 0.5 ? "world" : "hello";
また、issueでは特に触れられていませんが、このようにユニオン型のin that order
をサポートすることで、順番制限付きオブジェクト型とkeyof
の関係もサポートされることが予想できます。
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の原文を見に行きましょう。
FAQ
Q. 嘘をついていいのは午前中までという説もありますが?
A. アメリカはまだ午前中ですよ。