月曜日, 4月 28, 2025
Google search engine
ホームニューステックニュースSwiftのLiteralとLiteral Protocol

SwiftのLiteralとLiteral Protocol


Swiftには様々なLiteralがあります。

ここで大事なのが、Swiftにおいて「Literal」と「Literalから生成される値の型」は別の概念だということです。

https://qiita.com/koher/items/cbe232782b9943715c9b

SwiftにおいてLiteralはどんな型の値でも生成することができます。そのため、Literalの記述だけを見て「XX型だ」と言い切ることはできません。
例えばSwiftUI.TextStringLiteralを用いて初期化するコードを考えてみます。

ここで、"Hello"から結果的に生成される値の型はStringではありません。LocalzedStringKeyです。

選択されているinit

init(
    _ key: LocalizedStringKey,
    tableName: String? = nil,
    bundle: Bundle? = nil,
    comment: StaticString? = nil
)

このLocalizedStringKeyのようにLiteralから初期化可能な型を作るためには、型をLiteral Protocolに準拠させる必要があります。
例えばLocalizedStringKeyExpressibleByStringInterpolationに準拠しています。

例として、以下のようにIntegerLiteralからStringを初期化できるようにしてみます。

この時StringExpressibleByIntegerLiteralに準拠させる必要があります。
まず、ExpressibleByIntegerLiteralの定義を見てみます。

public protocol ExpressibleByIntegerLiteral {
  
  
  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 {
    
    
    public init(integerLiteral value: Int) {
        self.init(describing: value)
    }
}




let number: String = 10
print(number) 

以上がLiteral Protocolの概要です。Literal/Literal Protocolは複数ありますが、どれも中間表現が必要な点と、準拠の流れは基本的には同じです。

Swift 6.1時点でのLiteral Protocol一覧を簡単にまとめます。(RegexLiteralはLiteral Protocolがないので除外)
参考元は CompilerProtocols.swift

ExpressibleByNilLiteral

定義

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) 

ExpressibleByIntegerLiteral

定義

public protocol ExpressibleByIntegerLiteral {
  
  
  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) 

ExpressibleByFloatLiteral

定義

public protocol ExpressibleByFloatLiteral {
  
  
  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) 

ExpressibleByBooleanLiteral

定義

public protocol ExpressibleByBooleanLiteral {
  
  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" 
"ab" 
"🔴" 
"🖐️" 
"(1)" 

UnicodeとSwiftのUnicode表現についてはTSPL-Unicode-Representations-of-Stringsら辺を参考

定義

public protocol ExpressibleByUnicodeScalarLiteral {
  
  
  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の表現であるため。)

https://developer.apple.com/documentation/swift/character

"a" 
"ab" 
"🔴" 
"🖐️" 
"(1)" 

なおExpressibleByExtendedGraphemeClusterLiteralExpressibleByUnicodeScalarLiteralの子protocolである。

定義

public protocol ExpressibleByExtendedGraphemeClusterLiteral
  : ExpressibleByUnicodeScalarLiteral {
  
  
  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" 
"ab" 
"🔴" 
"🖐️" 
"(1)" 

"""
    let a = 10
"""


#"(10)"# 


#"""
    let a = "(10)"
"""#

なおExpressibleByStringLiteralExpressibleByExtendedGraphemeClusterLiteralの子protocolである。

定義

public protocol ExpressibleByStringLiteral
  : ExpressibleByExtendedGraphemeClusterLiteral {
  
  
  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) 

ExpressibleByStringInterpolation

文字列展開を含むStringLiteral。

"a" 
"ab" 
"🔴" 
"🖐️" 
"(1)" 

なおExpressibleByStringInterpolationExpressibleByStringLiteralの子protocolである。

定義

中間表現はStringInterpolationProtocol
この中間表現の目的は他のLiteral Protocolとは違う。(10)のような文字列展開は関数呼び出しに静的変換されており、StringInterpolationProtocolはその呼び出し先関数を定義するレイヤーである。

参考: SE-0228 Fix ExpressibleByStringInterpolation

デフォルトの実装としてDefaultStringInterpolationが用意されている。

public protocol ExpressibleByStringInterpolation
  : ExpressibleByStringLiteral {
  
  
  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)

  
  
  
  
  
}

利用例

extension JSON: ExpressibleByStringInterpolation {
    init(stringInterpolation: DefaultStringInterpolation) {
        self = .string(stringInterpolation.description)
    }
}

let json: JSON = "(1)"
print(json) 

また、DefaultStringInterpolationにextensionでappendInterpolationを追加するか、StringInterpolationProtocol自体を自分で実装することで任意の文字列展開の挙動を実現できます。コレは記事冒頭で言及したSwiftUI.Textにも利用されているテクニックです。

https://qiita.com/ensan_hcl/items/d9b0fdadec8ca77a556c

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 = "🖐️"

print("🖐️ is (hand)") 


printUnicodeScalar("🖐️ is (unicodeScalarOf: hand)") 

ExpressibleByArrayLiteral

定義

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) 

ExpressibleByDictionaryLiteral

定義

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) 

余談ですが今までの利用例の実装で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) 

_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 SIMD4Float>: @retroactive _ExpressibleByColorLiteral {
    public init(_colorLiteralRed red: Float, green: Float, blue: Float, alpha: Float) {
        self.init(red, green, blue, alpha)
    }
}

let simd4: SIMD4Float> = #colorLiteral(red: 0.1215686277, green: 0.01176470611, blue: 0.4235294163, alpha: 1)
print(simd4) 

_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")

https://github.com/swiftlang/swift/blob/main/docs/Literals.md

https://qiita.com/freddi_/items/044bdf6defbbe434a8e2

フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -
Google search engine

Most Popular

Recent Comments