目的
↑ この記事の続きとなります。
Swiftには、Character型を直接意味する リテラルは 存在しません。
つまり、C/C++言語のように、char型は 'A'(シングルクォーテーションで囲む)、string(char*)型は "A"(ダブルクォーテーションで囲む)といった書き方ができません。
標準で書ける(単純型の)リテラルは、
数値風な 10 や 3.14 か、文字列風な "abcdefg" の タイプしか存在しません。
敢えて ‘風な’と付け加えた理由は、実際の型は ExpressibleByXXXXLiteralプロトコル によって定義され、見た目と実態は必ずしも一致しないからです。例えば、数値を書いて 文字列型にすることもできます。
- 例;数値を文字列型にする
let a: MyStringLiteral = 123 print(type(of: a), a.debugDescription) //String "123"
今回のテーマは、「Character型リテラルを定義する」です。
しかし、ExpressibleByXXXXLiteralプロトコル を定義してCharacter型にすることではありません。型アンテーションは すでにあります。
- 例;Character型とする
let a: Character = "a" print(type(of: a), a.debugDescription) //Character "a"
理想は、(シングルクォーテーションで囲む)'a'と書きたいところですが、これは 現在のSwift言語仕様上 無理な話なので、PythonやC++などのRaw文字列風な書き方で実現したいと思います。
- 目指す書き方
let a = r"a"
実装の検討
関数的な書き方なら、今でも普通に書けます。
-
let a = Character("a") // Character.init
Characterが長いならTypealiasや関数定義で 1文字にできます。
-
typealias C = Character let a = C("a") // Character.init
あくまで 文字列リテラルで表現したいので、次の書式で書ける方法を考えます。
-
let a = c"a"しかし、現在のSwift言語仕様上、このままは 無理ですね。
Cの代わりに 何らかの演算子を使った表現なら、
-
let a = ^"a"この書式であれば、文字列に対する
^前置演算子を定義することで 実現できます。prefix operator ^ func ^(arg: String) -> Character { if arg.isEmpty { Character(UnicodeScalar(0)) } else { Character(String(arg.first!)) } } let a = ^"a" print(type(of: a), a.debugDescription) //Character "a"
見た目はOKですが、次の点を変更します。
-
^を含む既存の演算子の利用は、他の演算子拡張との兼ね合いで避けたい - func定義をインライン化して、少しでも変換ロスをなくしたい
最終的な定義
・ Character Literal
一部のUnicode文字を 演算子として定義できる言語仕様を活かして、以下のように定義しました。
-
prefix operator © // keystroke option+G @inlinable @inline(__always) prefix func ©(arg: String) -> Character { if arg.isEmpty { Character(UnicodeScalar(0)) } else { Character(String(arg.first!)) } } @inlinable @inline(__always) prefix func ©(arg: Int) -> Character { if let scalar = UnicodeScalar(arg) { Character(scalar) } else { Character(UnicodeScalar(0)) } }- 演算子は
©(コピーライトマーク)とする
( Macであれば、⌥G(option + G)でタイプ可 ) - インライン展開を指示(実効性は不明)
- IntでUnicode値を指定する
©前置演算子も定義
- 演算子は
-
使い方
let a = ©"A" print(type(of: a), a.debugDescription) //Character "A"let constellation = 0x2648 ... 0x2653 for c in constellation { print("U+\(String(format: "%X", c)) \(©c)") } //U+2648 ♈ //U+2649 ♉ //U+264A ♊ //U+264B ♋ //U+264C ♌ //U+264D ♍ //U+264E ♎ //U+264F ♏ //U+2650 ♐ //U+2651 ♑ //U+2652 ♒ //U+2653 ♓
・ Character型をStrideableに準拠させる②
前回の記事で書いたコードは、ASCIIコード限定でした。今回は、これを Unicode 全体に拡張します。
-
extension Character: @retroactive Strideable { public func advanced(by n: Int) -> Self { var nextValue = self.value + n while 0 < nextValue && nextValue <= 0x10FFFF { if let scalar = UnicodeScalar(nextValue) { return Character(scalar) } nextValue &+= 1 } return Character(UnicodeScalar(0)) } public func distance(to x: Self) -> Int { return x.value - self.value } var value: Int { Int(self.unicodeScalars.first!.value) } }
- 使い方
let constellation = ©0x2648 ... ©0x2653 for c in constellation { print("U+\(String(format: "%X", c.value)) \(c)") } //U+2648 ♈ //U+2649 ♉ //U+264A ♊ //U+264B ♋ //U+264C ♌ //U+264D ♍ //U+264E ♎ //U+264F ♏ //U+2650 ♐ //U+2651 ♑ //U+2652 ♒ //U+2653 ♓
結果
Character型リテラルを定義することが目的でしたが、厳密には達成できていません。しかし、「コードを書く上での見た目が同じ」という点にて 達成できたと評価します(自己マンです)。
-
let a = c"a" //目指す書き方 let a = ©"A" //今回実現した書き方
以上