SwiftのLiteralとLiteral Protocol
Literal
Swiftには様々なLiteralがあります。
nil // NilLiteral
10 // IntegerLiteral
true // BooleanLiteral
"hello" // StringLiteral
[] // ArrayLiteral
ここで大事なのが、Swiftにおいて「Literal」と「Literalから生成される値の型」は別の概念だということです。
SwiftにおいてLiteralはどんな型の値でも生成することができます。そのため、Literalの記述だけを見て「XX型だ」と言い切ることはできません。
例えばSwiftUI.Text
をStringLiteral
を用いて初期化するコードを考えてみます。
Text("Hello")
ここで、"Hello"
から結果的に生成される値の型はString
ではありません。LocalzedStringKey
です。
init(
_ key: LocalizedStringKey,
tableName: String? = nil,
bundle: Bundle? = nil,
comment: StaticString? = nil
)
このLocalizedStringKey
のようにLiteralから初期化可能な型を作るためには、型をLiteral Protocolに準拠させる必要があります。
例えばLocalizedStringKey
はExpressibleByStringInterpolation
に準拠しています。
Literal Protocol
例として、以下のようにIntegerLiteralからStringを初期化できるようにしてみます。
let string: String = 10
この時String
をExpressibleByIntegerLiteral
に準拠させる必要があります。
まず、ExpressibleByIntegerLiteral
の定義を見てみます。
public protocol ExpressibleByIntegerLiteral {
/// The standard library integer and floating-point types are all valid types
/// for `IntegerLiteralType`.
associatedtype IntegerLiteralType: _ExpressibleByBuiltinIntegerLiteral
init(integerLiteral value: IntegerLiteralType)
}
IntegerLiteralType
というassociatedTypeがあります。これはLiteralから変換する際の中間表現に使う型です。さらにこの中間表現には_ExpressibleByBuiltinIntegerLiteral
の制約があります。
究極的(直接的)にLiteralから型に変換するにはコンパイラ上で実装が必要なので、コンパイラ上に実装があり、Literalから直接変換できる型(=_ExpressibleByBuiltinIntegerLiteral
)の中間表現を一旦噛ます必要があるわけです。
(厳密にはコンパイラ上で実装があってLiteralから直接変換できるのはBuiltin.IntX
型とかで、IntとかはそのBuiltin.IntX
を内部表現として持つので、Literalから直接変換できる=_ExpressibleByBuiltinIntegerLiteral
、という構造。)
ExpressibleByIntegerLiteral
のケースだと(コメントにあるように)stdlibの整数型と浮動小数点型が_ExpressibleByBuiltinIntegerLiteral
に準拠しています。
実際に実装してみると以下のようになります。
extension String: @retroactive ExpressibleByIntegerLiteral {
// LiteralをIntで一旦受け取り、Stringに変換する
// Int32でもUIntでもFloatでもOK
public init(integerLiteral value: Int) {
self.init(describing: value)
}
}
// `10`(IntegerLiteral)が
// `_ExpressibleByBuiltinIntegerLiteral`によってIntに変換された後
// `ExpressibleByIntegerLiteral`によってStringに変換される
let number: String = 10
print(number) // 10
以上がLiteral Protocolの概要です。Literal/Literal Protocolは複数ありますが、どれも中間表現が必要な点と、準拠の流れは基本的には同じです。
Literal Protocol一覧
Swift 6.1時点でのLiteral Protocol一覧を簡単にまとめます。(RegexLiteralはLiteral Protocolがないので除外)
参考元は CompilerProtocols.swift
ExpressibleByNilLiteral
nil
定義
public protocol ExpressibleByNilLiteral: ~Copyable, ~Escapable {
@lifetime(immortal)
init(nilLiteral: ())
}
中間表現は固定で()
(Void
)
利用例
enum JSON {
case null
}
extension JSON: CustomStringConvertible {
var description: String {
switch self {
case .null: "null"
}
}
}
extension JSON: ExpressibleByNilLiteral {
init(nilLiteral: ()) {
self = .null
}
}
let value: JSON = nil
print(value) // null
ExpressibleByIntegerLiteral
10 // 10進数
0b10 // 2進数
0o07 // 8進数
0x0A // 16進数
定義
public protocol ExpressibleByIntegerLiteral {
/// The standard library integer and floating-point types are all valid types
/// for `IntegerLiteralType`.
associatedtype IntegerLiteralType: _ExpressibleByBuiltinIntegerLiteral
init(integerLiteral value: IntegerLiteralType)
}
中間表現は_ExpressibleByBuiltinIntegerLiteral
: stdlibの整数型と浮動小数点型
利用例
enum JSON {
case null
case int(Int)
}
extension JSON: CustomStringConvertible {
var description: String {
switch self {
...
case .int(let int): int.description
}
}
}
extension JSON: ExpressibleByIntegerLiteral {
init(integerLiteral value: Int) {
self = .int(value)
}
}
let value: JSON = 10
print(value) // 10
ExpressibleByFloatLiteral
1.0 // 10進数
1e4 // 10進数指数
0x1p4 // 16進数表記+2進数指数
定義
public protocol ExpressibleByFloatLiteral {
/// Valid types for `FloatLiteralType` are `Float`, `Double`, and `Float80`
/// where available.
associatedtype FloatLiteralType: _ExpressibleByBuiltinFloatLiteral
init(floatLiteral value: FloatLiteralType)
}
中間表現は_ExpressibleByBuiltinFloatLiteral
: stdlibの浮動小数点型Float
, Double
, Float80
利用例
enum JSON {
case null
case int(Int)
case double(Double)
}
extension JSON: CustomStringConvertible {
var description: String {
switch self {
...
case .double(let double): double.description
}
}
}
extension JSON: ExpressibleByFloatLiteral {
init(floatLiteral value: Double) {
self = .double(value)
}
}
let value: JSON = 10.0
print(value) // 10.0
ExpressibleByBooleanLiteral
false
true
定義
public protocol ExpressibleByBooleanLiteral {
/// A type that represents a Boolean literal, such as `Bool`.
associatedtype BooleanLiteralType: _ExpressibleByBuiltinBooleanLiteral
init(booleanLiteral value: BooleanLiteralType)
}
中間表現はExpressibleByBooleanLiteral
: Boolだけ
利用例
enum JSON {
case null
case int(Int)
case double(Double)
case bool(Bool)
}
extension JSON: CustomStringConvertible {
var description: String {
switch self {
...
case .bool(let bool): bool.description
}
}
}
extension JSON: ExpressibleByBooleanLiteral {
init(booleanLiteral value: Bool) {
self = .bool(value)
}
}
let value: JSON = false
print(value)
ExpressibleByUnicodeScalarLiteral
UnicodeScalarLiteral
は1つのUnicode Scalar(コードポイント)のみで構成されるStringLiteralのこと。
"a" // U+0061 ✅ Unicode Scalar一つ
"ab" // U+0061 U+0062 🔴 二文字, Unicode Scalar二つなのでUnicodeScalarLiteralではない
"🔴" // U+1F534 ✅ Unicode Scalar一つで表現される絵文字
"🖐️" // U+1F590 U+FE0F 🔴 Unicode Scalar二つで表現される絵文字なのでUnicodeScalarLiteralではない
"\(1)" // 🔴 String InterpolationはUnicodeScalarLiteralではない
UnicodeとSwiftのUnicode表現についてはTSPL-Unicode-Representations-of-Stringsら辺を参考
定義
public protocol ExpressibleByUnicodeScalarLiteral {
/// Valid types for `UnicodeScalarLiteralType` are `Unicode.Scalar`,
/// `Character`, `String`, and `StaticString`.
associatedtype UnicodeScalarLiteralType: _ExpressibleByBuiltinUnicodeScalarLiteral
init(unicodeScalarLiteral value: UnicodeScalarLiteralType)
}
中間表現は_ExpressibleByBuiltinUnicodeScalarLiteral
: Unicode.Scalar
, Character
, String
, StaticString
利用例
実用的な利用例が思いつかなかったので適当な実装
struct MyUnicodeScalar: ExpressibleByUnicodeScalarLiteral {
var value: UnicodeScalar
init(unicodeScalarLiteral value: UnicodeScalar) {
self.value = value
}
}
let a: MyUnicodeScalar = "a"
ExpressibleByExtendedGraphemeClusterLiteral
ExtendedGraphemeClusterLiteral
は1つのCharacter
で構成されるStringLiteral。
つまり実質ExpressibleByCharacterLiteral
。(Swift.Characterはextended grapheme clusterの表現であるため。)
"a" // U+0061 ✅ Unicode Scalar一つで一文字
"ab" // U+0061 U+0062 🔴 二文字なのでExtendedGraphemeClusterLiteralではない
"🔴" // U+1F534 ✅ Unicode Scalar一つで表現される絵文字
"🖐️" // U+1F590 U+FE0F ✅ Unicode Scalar二つだが一文字
"\(1)" // 🔴 String InterpolationはExtendedGraphemeClusterLiteralではない
なおExpressibleByExtendedGraphemeClusterLiteral
はExpressibleByUnicodeScalarLiteral
の子protocolである。
定義
public protocol ExpressibleByExtendedGraphemeClusterLiteral
: ExpressibleByUnicodeScalarLiteral {
/// Valid types for `ExtendedGraphemeClusterLiteralType` are `Character`,
/// `String`, and `StaticString`.
associatedtype ExtendedGraphemeClusterLiteralType
: _ExpressibleByBuiltinExtendedGraphemeClusterLiteral
init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType)
}
中間表現は_ExpressibleByBuiltinExtendedGraphemeClusterLiteral
: Character
, String
, StaticString
。
利用例
実用的な利用例が思いつかなかったので適当な実装
struct MyChar {
var value: Character
}
extension MyChar: ExpressibleByExtendedGraphemeClusterLiteral {
init(extendedGraphemeClusterLiteral value: Character) {
self.value = value
}
}
let a: MyChar = "a"
let c: MyChar = "🔴"
let d: MyChar = "👨👨👧👧"
ExpressibleByStringLiteral
StringLiteral。複数文字も表現可能だが、文字列展開はまだできない。
"a" // U+0061 ✅
"ab" // U+0061 U+0062 ✅ 二文字以上でもOK
"🔴" // U+1F534 ✅
"🖐️" // U+1F590 U+FE0F ✅
"\(1)" // 🔴 String InterpolationはStringLiteralではない
// multiline
"""
let a = 10
"""
// raw string
#"\(10)"# // \(10)
// raw string multiline
#"""
let a = "\(10)"
"""#
なおExpressibleByStringLiteral
はExpressibleByExtendedGraphemeClusterLiteral
の子protocolである。
定義
public protocol ExpressibleByStringLiteral
: ExpressibleByExtendedGraphemeClusterLiteral {
/// Valid types for `StringLiteralType` are `String` and `StaticString`.
associatedtype StringLiteralType: _ExpressibleByBuiltinStringLiteral
init(stringLiteral value: StringLiteralType)
}
中間表現は_ExpressibleByBuiltinStringLiteral
: StringとStaticString。
利用例
enum JSON {
case null
case int(Int)
case double(Double)
case bool(Bool)
case string(String)
}
extension JSON: CustomStringConvertible {
var description: String {
switch self {
...
case .string(let string): "\"" + string + "\""
}
}
}
extension JSON: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self = .string(value)
}
}
let json = "hoge"
print(json) // "hoge"
ExpressibleByStringInterpolation
文字列展開を含むStringLiteral。
"a" // U+0061 ✅
"ab" // U+0061 U+0062 ✅
"🔴" // U+1F534 ✅
"🖐️" // U+1F590 U+FE0F ✅
"\(1)" // ✅ String InterpolationもOK
なおExpressibleByStringInterpolation
はExpressibleByStringLiteral
の子protocolである。
定義
中間表現はStringInterpolationProtocol
。
この中間表現の目的は他のLiteral Protocolとは違う。\(10)
のような文字列展開は関数呼び出しに静的変換されており、StringInterpolationProtocol
はその呼び出し先関数を定義するレイヤーである。
参考: SE-0228 Fix ExpressibleByStringInterpolation
デフォルトの実装としてDefaultStringInterpolation
が用意されている。
public protocol ExpressibleByStringInterpolation
: ExpressibleByStringLiteral {
/// The `StringLiteralType` of an interpolation type must match the
/// `StringLiteralType` of the conforming type.
associatedtype StringInterpolation: StringInterpolationProtocol
= DefaultStringInterpolation
where StringInterpolation.StringLiteralType == StringLiteralType
init(stringInterpolation: StringInterpolation)
}
public protocol StringInterpolationProtocol {
associatedtype StringLiteralType: _ExpressibleByBuiltinStringLiteral
init(literalCapacity: Int, interpolationCount: Int)
mutating func appendLiteral(_ literal: StringLiteralType)
// Informal requirement: Any desired appendInterpolation overloads, e.g.:
//
// mutating func appendInterpolation<T>(_: T)
// mutating func appendInterpolation(_: Int, radix: Int)
// mutating func appendInterpolation<T: Encodable>(json: T) throws
}
利用例
extension JSON: ExpressibleByStringInterpolation {
init(stringInterpolation: DefaultStringInterpolation) {
self = .string(stringInterpolation.description)
}
}
let json: JSON = "\(1)"
print(json) // "1"
また、DefaultStringInterpolation
にextensionでappendInterpolation
を追加するか、StringInterpolationProtocol
自体を自分で実装することで任意の文字列展開の挙動を実現できます。コレは記事冒頭で言及したSwiftUI.Text
にも利用されているテクニックです。
struct UnicodeScalarLog: ExpressibleByStringInterpolation, CustomStringConvertible {
var description: String
init(stringLiteral value: String) {
description = value
}
init(stringInterpolation: StringInterpolation) {
self.init(stringLiteral: stringInterpolation.storage)
}
}
extension UnicodeScalarLog {
struct StringInterpolation: StringInterpolationProtocol {
var storage: String = ""
init(literalCapacity: Int, interpolationCount: Int) {
let capacityPerInterpolation = 2
let initialCapacity = literalCapacity + interpolationCount * capacityPerInterpolation
storage.reserveCapacity(initialCapacity)
}
mutating func appendLiteral(_ literal: StringLiteralType) {
storage += literal
}
mutating func appendInterpolation(unicodeScalarOf value: String) {
let unicodeScalars = value.unicodeScalars.map {
"U+\($0.value)"
}.joined(separator: " ")
appendLiteral(unicodeScalars)
}
}
}
func printUnicodeScalar(_ log: UnicodeScalarLog) {
print(log)
}
let hand = "🖐️"
// Default String Interpolation
print("🖐️ is \(hand)") // 🖐️ is 🖐️
// UnicodeScalarLog.StringInterpolation
printUnicodeScalar("🖐️ is \(unicodeScalarOf: hand)") // 🖐️ is U+128400 U+65039
ExpressibleByArrayLiteral
[1, 2, 3]
定義
public protocol ExpressibleByArrayLiteral {
associatedtype ArrayLiteralElement
init(arrayLiteral elements: ArrayLiteralElement...)
}
中間表現は固定で中間表現は固定で可変超引数。ArrayLiteralElementは要素を指定するためのassociated type。
利用例
enum JSON {
case null
case int(Int)
case double(Double)
case bool(Bool)
case string(String)
case array([JSON])
}
extension JSON: CustomStringConvertible {
var description: String {
switch self {
...
case .array(let array):
"[" + array.map(\.description).joined(separator: ", ") + "]"
}
}
}
extension JSON: ExpressibleByArrayLiteral {
init(arrayLiteral elements: JSON...) {
self = .array(elements)
}
}
let json = [1, "10", nil]
print(json) // [1, "10", null]
ExpressibleByDictionaryLiteral
[1: 10, 2: 20]
定義
public protocol ExpressibleByDictionaryLiteral {
associatedtype Key
associatedtype Value
init(dictionaryLiteral elements: (Key, Value)...)
}
中間表現は固定でタプルの可変超引数。
利用例
enum JSON {
case null
case int(Int)
case double(Double)
case bool(Bool)
case string(String)
case array([JSON])
case object([String: JSON])
}
extension JSON: CustomStringConvertible {
var description: String {
switch self {
...
case .object(let object):
"{" + object.map { "\"" + $0 + "\": " + $1.description }.joined(separator: ",") + "}"
}
}
}
extension JSON: ExpressibleByDictionaryLiteral {
init(dictionaryLiteral elements: (String, JSON)...) {
self = .object(Dictionary(uniqueKeysWithValues: elements))
}
}
let value: JSON = [
"id": "123456",
"name": "taro"
]
print(value) // {"id": "123456","name": "taro"}
余談ですが今までの利用例の実装でJSON
型が完成しました
フルコード
enum JSON {
case null
case int(Int)
case double(Double)
case bool(Bool)
case string(String)
case array([JSON])
case object([String: JSON])
}
extension JSON: CustomStringConvertible {
var description: String {
switch self {
case .null: "null"
case .int(let int): int.description
case .double(let double): double.description
case .bool(let bool): bool.description
case .string(let string): "\"" + string + "\""
case .array(let array):
"[" + array.map(\.description).joined(separator: ", ") + "]"
case .object(let object):
"{" + object.map { "\"" + $0 + "\": " + $1.description }.joined(separator: ",") + "}"
}
}
}
extension JSON: ExpressibleByNilLiteral {
init(nilLiteral: ()) {
self = .null
}
}
extension JSON: ExpressibleByIntegerLiteral {
init(integerLiteral value: Int) {
self = .int(value)
}
}
extension JSON: ExpressibleByFloatLiteral {
init(floatLiteral value: Double) {
self = .double(value)
}
}
extension JSON: ExpressibleByBooleanLiteral {
init(booleanLiteral value: Bool) {
self = .bool(value)
}
}
extension JSON: ExpressibleByArrayLiteral {
init(arrayLiteral elements: JSON...) {
self = .array(elements)
}
}
extension JSON: ExpressibleByDictionaryLiteral {
init(dictionaryLiteral elements: (String, JSON)...) {
self = .object(Dictionary(uniqueKeysWithValues: elements))
}
}
extension JSON: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self = .string(value)
}
}
extension JSON: ExpressibleByStringInterpolation {
init(stringInterpolation: DefaultStringInterpolation) {
self = .string(stringInterpolation.description)
}
}
let value: JSON = [[
"id": 10,
"name": "hoge",
"price": 1000,
"user": nil
], [
"id": 11,
"name": "fuga",
"price": 1100,
"user": [
"id": "123456",
"name": "taro"
]
]]
print(value) // [{"user": null,"id": 10,"name": "hoge","price": 1000}, {"name": "fuga","price": 1100,"id": 11,"user": {"id": "123456","name": "taro"}}]
_ExpressibleByColorLiteral
Playground literalと呼ばれるXcode側にUIで対応のある特殊なliteral。
ソース上でみるとただの関数呼び出しのように見えるが、Xcode上だと特殊なUIで表示される。
#colorLiteral(red: 0.1215686277, green: 0.01176470611, blue: 0.4235294163, alpha: 1)
これ
定義
public protocol _ExpressibleByColorLiteral {
init(_colorLiteralRed red: Float, green: Float, blue: Float, alpha: Float)
}
利用例
extension SIMD4<Float>: @retroactive _ExpressibleByColorLiteral {
public init(_colorLiteralRed red: Float, green: Float, blue: Float, alpha: Float) {
self.init(red, green, blue, alpha)
}
}
let simd4: SIMD4<Float> = #colorLiteral(red: 0.1215686277, green: 0.01176470611, blue: 0.4235294163, alpha: 1)
print(simd4) // SIMD4<Float>(0.12156863, 0.011764706, 0.42352942, 1.0)
_ExpressibleByImageLiteral
Playground literalの一つ。
#imageLiteral(resourceName: "hogehoge.png")
定義
public protocol _ExpressibleByImageLiteral {
init(imageLiteralResourceName path: String)
}
利用例
struct MyImage {
var path: String
}
extension MyImage: _ExpressibleByImageLiteral {
init(imageLiteralResourceName path: String) {
self.path = path
}
}
let image: MyImage = #imageLiteral(resourceName: "hogehoge.png")
print(image.path)
_ExpressibleByFileReferenceLiteral
Playground literalの一つ。
#fileLiteral(resourceName: "resource.txt")
定義
public protocol _ExpressibleByFileReferenceLiteral {
init(fileReferenceLiteralResourceName path: String)
}
利用例
struct MyFile {
var path: String
}
extension MyFile: _ExpressibleByFileReferenceLiteral {
init(fileReferenceLiteralResourceName path: String) {
self.path = path
}
}
let file: MyFile = #fileLiteral(resourceName: "resource.txt")
参考
Discussion