InlineArray Type Sugar
01 何が問題だったのか
SE-0453 で導入された InlineArray は、要素数を型パラメータに含む固定長配列です。ヒープアロケーションを伴わず、要素をインラインに保持するため、パフォーマンスが重要な場面で Array の代替となります。
しかし、通常の Array が [Int] という糖衣構文を持つのに対し、InlineArray には糖衣構文がなく、次のように型名を直接書く必要がありました。
let fiveIntegers: InlineArray<5, Int> = .init(repeating: 99)
これは Array の [Int] と比べて記述が冗長で、特に多次元になると顕著に読みづらくなります。
let fiveByFive: InlineArray<5, InlineArray<5, Int>> = .init(repeating: .init(repeating: 99))
「Array を使うべき」という印象を与えてしまう
C・C++・Rust・Go・Java など、Swift と同じカテゴリの多くの言語は固定長配列に簡潔な構文を与えています。Swift では逆に、動的配列である Array にだけ糖衣構文があり、固定長の InlineArray は冗長な型名のままでした。
この非対称性は、「Array こそが第一候補で、InlineArray はごく稀なマイクロ最適化である」という誤った印象を読者に与えます。実際には、3次元ベクトルのように要素数が静的に決まる値を [Double] で表すと、ヒープアロケーション、BitwiseCopyable ではなくなること、境界チェックや一意性チェックなど、避けられたはずのパフォーマンス上の不利を負うことになります。InlineArray と Array は性能特性の異なる対等な選択肢であり、どちらも同じように扱いやすい糖衣構文を備えているべきでした。
02 どのように解決されるのか
InlineArray に対して、サイズと要素型を of で区切る糖衣構文 [サイズ of 要素型] を導入します。
// 従来
let fiveIntegers: InlineArray<5, Int> = .init(repeating: 99)
// 新しい糖衣構文
let fiveIntegers: [5 of Int] = .init(repeating: 99)
of は “an array of five ints”(5個の Int の配列)という英語の自然なフレーズに近い読み方ができ、in や let と同じように短い文脈依存のキーワードとして機能します。
使い方
糖衣構文は InlineArray と完全に等価なので、ネストや型推論、右辺での利用など、元の型が書ける場所ならどこでも使えます。
// ネスト
let fiveByFive: [5 of [5 of Int]] = .init(repeating: .init(repeating: 99))
// 文脈からの型推論(サイズや要素型に _ を使える)
let fiveIntegers: [5 of _] = .init(repeating: 99)
let fourBytes: [_ of Int8] = [1, 2, 3, 4]
let fourIntegers: [_ of _] = [1, 2, 3, 4]
// 右辺での利用
let fiveDoubles = [5 of _](repeating: 1.23)
// 型として使える場所すべて
[5 of Int](repeating: 99)
MemoryLayout<[5 of Int]>.size
unsafeBitCast((1, 2, 3), to: [3 of Int].self)
サイズの位置に書けるのは、現状の InlineArray の仕様に沿って整数リテラルまたは整数型パラメータに限られます。将来 InlineArray 側で任意の式が許されるようになれば、この糖衣構文も同様に拡張される想定です。
記法上のルール
ofの前後には空白が必要です。[5of Int]は書けません。- 空白の数は揃える必要はありません。
[5 of Int]のように片側だけ多くても構いません。 ofの後ろには改行を置けますが、前に置くことはできません。これは文法上曖昧にはならないものの、パーサのエラー回復がしやすくなるように意図された制約です。
of を選んだ理由
セパレータの候補としては x・*・;・区切り記号なし、などが検討されました。of が選ばれたのは主に次の理由からです。
- 将来、式をサイズに書けるようになったとき、
[5 * N * Int]は*演算子と紛らわしくなるのに対し、[5 * N of Int]は明確に読めます。 - 将来、値版の糖衣構文(後述)が導入されたとき、
[5 of 5]は「5個の5」と自然に読めますが、[5 x 5]や[5 * 5]は掛け算と混同されます。 - 完全推論した
[_ of _]が、[_ x _]や[_ * _]と比べて ASCII アート感が少なく視認しやすいです。
Future Directions(今後の見通し)
Proposal 内では、同じ of を値側にも展開する方向性が示唆されています。実現が約束されているわけではなく、別 Proposal で改めて検討される未来の話ですが、記法の設計はこれを意識して選ばれています。
// 型が [5 of Int] に推論される
let fiveInts = [5 of 99]
// [Int] に対して .init(repeating: 99, count: 5) と等価
let dynamic: [Int] = [5 of 99]
また、[5 of [5 of Int]] のような多次元表記を [5 of 5 of Int] のようにフラットに書けるようにする拡張も、今後の検討対象として挙げられています。