Rename Literal Syntax Protocols
01 何が問題だったのか
Swift 2 まで、リテラルで書かれた値を自分の型として扱えるようにするためのプロトコルは、次のような *LiteralConvertible という名前を持っていました。
public protocol NilLiteralConvertible { ... }
public protocol BooleanLiteralConvertible { ... }
public protocol FloatLiteralConvertible { ... }
public protocol IntegerLiteralConvertible { ... }
public protocol UnicodeScalarLiteralConvertible { ... }
public protocol ExtendedGraphemeClusterLiteralConvertible { ... }
public protocol StringLiteralConvertible { ... }
public protocol StringInterpolationConvertible { ... }
public protocol ArrayLiteralConvertible { ... }
public protocol DictionaryLiteralConvertible { ... }
これらの名前には二つの問題がありました。
Convertible の意味が一貫していない
標準ライブラリの中で Convertible は二つの異なる意味で使われていました。*LiteralConvertible ではリテラル「から」変換されることを指していましたが、一方で CustomStringConvertible / CustomDebugStringConvertible では String 「へ」変換されることを指していました。同じ語が逆向きの関係を表していたため、標準ライブラリのネーミング慣習にならって独自プロトコルを命名しようとした開発者に混乱を生んでいました。
そもそも「変換」ではない
標準ライブラリチームからは、そもそもこれらは変換のためのプロトコルではない、という指摘がなされていました。型システム上に IntegerLiteral のような「リテラル型」が存在するわけではなく、整数リテラルはそのまま Int などの対応する型の値として扱われます。つまり、IntegerLiteralConvertible に適合しているからといって、その型の値をリテラルから変換して作れるわけではありません。次の例のように、ジェネリックな文脈でイニシャライザ経由でインスタンスを作ろうとしても通りません。
func f<T: IntegerLiteralConvertible>() -> T {
return T(integerLiteral: 43) // Error
return T(43) // Also an Error
}
一方で、リテラルをそのまま書けばその型の値として扱われます。
func f<T: IntegerLiteralConvertible>() -> T {
return 43 // OK
}
このプロトコルが表しているのは「その型のインスタンスをリテラルとして 書ける 」という性質であって、「リテラルから変換 できる 」という性質ではありません。Convertible という語はこの点でも誤解を招くものでした。
02 どのように解決されるのか
該当するプロトコルを、すべて ExpressibleBy*Literal という形式の名前にリネームします。意味合いとしては「その型のインスタンスが *Literal で書ける(=表現できる)」であることを、名前そのもので表します。プロトコルの要件(必要なイニシャライザなど)は一切変更されず、純粋な名前変更です。
新しい名前は次のとおりです。
public protocol ExpressibleByNilLiteral { ... }
public protocol ExpressibleByBooleanLiteral { ... }
public protocol ExpressibleByFloatLiteral { ... }
public protocol ExpressibleByIntegerLiteral { ... }
public protocol ExpressibleByUnicodeScalarLiteral { ... }
public protocol ExpressibleByExtendedGraphemeClusterLiteral { ... }
public protocol ExpressibleByStringLiteral { ... }
public protocol ExpressibleByStringInterpolation { ... }
public protocol ExpressibleByArrayLiteral { ... }
public protocol ExpressibleByDictionaryLiteral { ... }
利用側のコードでは、適合宣言やジェネリック制約で使っていた旧名を新名に置き換えるだけで済みます。たとえば次のように書き換えます。
// Before (Swift 2)
extension MyInt: IntegerLiteralConvertible {
init(integerLiteral value: Int) { ... }
}
// After (Swift 3)
extension MyInt: ExpressibleByIntegerLiteral {
init(integerLiteral value: Int) { ... }
}
これにより、ExpressibleBy*Literal が「リテラル記法で書ける」、CustomStringConvertible などの Convertible が「別の型への変換」を表す、という形で標準ライブラリ内の命名の向きが揃います。*LiteralConvertible を参照しているすべてのコードは新名への書き換えが必要ですが、名前以外の使い方に変化はありません。