Swift Digest
SE-0041 | Swift Evolution

Updating Protocol Naming Conventions for Conversions

Proposal
SE-0041
Authors
Matthew Johnson, Erica Sadun
Review Manager
Chris Lattner
Status
Rejected

01 何が問題だったのか

Swift 2.x 当時、標準ライブラリのプロトコル名には一定の接尾辞の慣習がありました。たとえば「何らかの型に振る舞いを追加するもの」には -able、「イテレートできるもの」には Sequence といった具合です。ところが、変換(conversion)に関わるプロトコルだけは接尾辞の意味が揺れており、名前から役割を読み取りにくい状態になっていました。本提案は、変換系プロトコルの命名規約を整理し、接尾辞と「変換の向き」を一対一で対応付けようとしたものでした。

Convertible が二つの意味で使われていた

標準ライブラリの Convertible という接尾辞には、相反する向きの意味が同居していました。

  • 大半の Convertible プロトコル(IntegerLiteralConvertibleStringLiteralConvertible など)は、名前に含まれる型「から」自身への変換、つまり「整数リテラルから自身を作れる」といったfrom方向の契約を表しています。
  • 一方で CustomStringConvertibleCustomDebugStringConvertible は逆で、自身「から」文字列「への」変換、つまりto方向の契約を表しています。

同じ Convertible という語なのに、プロトコルによって矢印の向きが逆になっているため、名前だけ見て役割を判断できない、というのが中心的な不満でした。

双方向変換を表す語が別にあった

さらに、自身と別の型との間で双方向に変換できる RawRepresentable は、Convertible ではなく Representable という別の接尾辞を採用していました。結果として、

  • from方向の変換: Convertible(多数)
  • to方向の変換: Convertible(2つだけ)
  • 双方向の変換: RepresentableRawRepresentable

という、接尾辞と意味の対応が崩れた状態になっていました。

ユーザー定義プロトコルも規約を欠いていた

標準ライブラリ自身の命名が揺れているため、サードパーティのコードでも ConvertibleRepresentable は各自の好みで使われており、接尾辞から役割を推測する手がかりにはなっていませんでした。Swift は過去にも LogicValueBooleanTypePrintable / DebugPrintableCustomStringConvertible / CustomDebugStringConvertibleExtensibleCollectionTypeRangeReplaceableCollectionType への吸収など、名前をより明確な方向へ整える更新を繰り返してきており、変換系プロトコルもその流れで整理しようというのがこの提案の出発点でした。

02 どのように解決されるのか

この提案は Rejected(却下) となりました。したがって、Swift の変換系プロトコルの接尾辞は IntegerLiteralConvertible などを含めて提案時点のまま残されました。その後の整理の中で、リテラル系プロトコルは最終的に別の形(ExpressibleByIntegerLiteral など ExpressibleBy- への改名)で整えられることになります。

提案されていた内容(却下されたもの)

提案は段階的に二つの案を示していました。

最初の案は、変換の3方向それぞれに異なる接尾辞を割り当てるものでした。

  • Creatable: 名前に含まれる型「から」自身を作る契約(from方向)。たとえば IntegerLiteralConvertibleIntegerLiteralCreatable に改名される想定でした。
  • Representable: 自身「から」名前に含まれる型を取り出す契約(to方向)。CustomStringConvertibleCustomStringRepresentable に。
  • Convertible: 双方向の変換を表す契約。RawRepresentableRawConvertible に。

次の案は、標準ライブラリのデザインチームからのフィードバックを受けて整理を進めた版で、規約を2つに絞っています。

  • Initializable: Creatable の置き換え。イニシャライザでもファクトリメソッドでも、「名前の型から自身を作れる」契約を包括的に表します。たとえば ArrayLiteralConvertibleArrayLiteralInitializable に。
  • Representable: 「自身から名前の型を取り出せる」契約。双方向かどうかは保証しないかわりに、CustomStringConvertible / CustomDebugStringConvertible / RawRepresentable をすべて Representable 系へ寄せます。

この更新案では双方向変換を表す独立した接尾辞は用意されず、「そうしたパターンが現れたら Isomorphic という語を将来のために予約しておく」と Future Directions に留める形になっていました。

却下の背景

デザインチームは、提案の問題意識(Convertible の向きが揺れている)自体には同意しつつも、

  • ConvertibleRepresentable を入れ替えても方向性の明確さは大きくは変わらないこと
  • リテラル系(XxxLiteralConvertible)以外で命名規約を固めるのに十分な実例がまだ揃っていないこと
  • むしろ XxxLiteralConvertible 系こそ名前を見直す価値があり、単なる接尾辞の差し替えより踏み込んだ整理(たとえば「リテラル構文で表現できる」ことを示す別の語彙への置き換え)のほうが望ましいこと

といった観点から、提案された形での接尾辞規約の採用には同意しませんでした。本提案は却下された一方で、課題意識はその後のリテラル系プロトコル群の改名(後述)という別の形で受け止められることになります。

現在の Swift での状況

リテラル系プロトコルは、本提案の却下後に提出された別の提案(SE-0115)によって ExpressibleBy- 接頭辞を使う名前へ整理されました。たとえば IntegerLiteralConvertibleExpressibleByIntegerLiteral に、StringLiteralConvertibleExpressibleByStringLiteral に改名され、現在もその形で使われています。

// 現在の Swift
extension MyNumber: ExpressibleByIntegerLiteral {
    init(integerLiteral value: Int) {
        self.init(value)
    }
}

let n: MyNumber = 42 // 整数リテラルから MyNumber を初期化できる

CustomStringConvertibleCustomDebugStringConvertible は改名されず、そのままの名前で残っています。RawRepresentable も名前を変えずに維持されています。

結果として、「接尾辞の意味を一つに統一する」という本提案の目標は達成されていませんが、

  • from方向(リテラル由来の初期化)は ExpressibleBy- 接頭辞
  • to方向は CustomXxxConvertible
  • 双方向は RawRepresentable

という、接尾辞/接頭辞の使い分けによる現行の命名に落ち着いています。ライブラリ作者としては、独自プロトコルを命名する際に「名前から矢印の向きを読み取れるようにする」という本提案が掲げた精神は依然として有用で、たとえば「リテラルから作れる」という契約には ExpressibleBy- に倣った名前を付ける、といった形で参考にできます。