[Swift] CharacterSetはCharacterのsetではありませんよ？

# `CharacterSet`は`Character`の set **ではない**
「そんなの知ってる」と思った貴方は基本的にこの記事を読む必要はありません。一方で「それってどういう意味？」と思った方がいらっしゃれば、ここでは[`CharacterSet`](https://developer.apple.com/documentation/foundation/characterset)周りでバグを作らないために知っておいた方が良いだろう内容を書いておきますので、多少はお役に立てるかもしれません。
`CharacterSet`は**将来的に本当に`Character`のsetになる可能性はある**ので、その時のために備えておきましょうという意味もあります。

# おさらい: `Unicode.Scalar`と`Character`の違い
Swiftが登場した当初、`String`を扱おうとした猛者たちの阿鼻叫喚が多数聞かれたかと思いますが、それもSwiftのバージョンが4.0となった今はだいぶ落ち着いたことでしょう。それでも一応は[`Unicode.Scalar`](https://developer.apple.com/documentation/swift/unicode.scalar)と[`Character`](https://developer.apple.com/documentation/swift/character)の違いをおさらいしておきましょう。ただ、そもそも、Unicode自体を知っていれば理解することは特に難しいことではありません。

## `Unicode.Scalar`
その名の通り`Unicode.Scalar`は「ユニコードスカラ値」を示す`struct`です。ユニコードスカラ値とは、~~[Wikipediaをみてください](https://ja.wikipedia.org/wiki/Unicode)~~Unicodeが文字または文字の部品に割り当てた数値のことで、0から1114111までの1114112個あります。たとえばアルファベットの"A"はU+0041(十進数で65)です。
ただ、上にも書いた通り、スカラ値は文字だけでなく"文字の部品"にも割り当てられているというところに注意が必要です。
ひらがなの「が」を考えてみましょう。Unicodeにある`HIRAGANA LETTER GA`には`U+304C`が割り当てられています。一方で、「が」は「か」に「゛(濁点)」がついたものでもあります。「か(`HIRAGANA LETTER KA`)」には`U+304B`、「゛(`COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK`)」は`U+3099`が割り当てられており[^dakuten]、これらを並べたもの`U+304B U+3099`も「が」なのです。すなわち、ユニコードスカラ値を複数並べて一文字を表すということもあるのです。そして、`U+304C`と`U+304B U+3099`はどちらも「が」なので同じであるとみなしてほしいと思うのが人の子でしょう。そこで登場するのが`Character`なのです。

[^dakuten]: ちなみに、`U+309B`にも濁点(KATAKANA-HIRAGANA VOICED SOUND MARK)がありますが、これは"濁点という記号を表す文字"なので`U+304B U+309B`と並べても「が」ではなく「か゛」という二文字となります。

## `Character`
上で説明した通り、ユニコードスカラ値が1個であろうと2個であろうと5個であろうと、それが一文字を表すのであれば一文字として扱いたいですよね？そのための`struct`が`Character`というわけです。`Character`では、その文字がいくつのユニコードスカラ値から成り立っているかは関係なく、同じ文字を表しているならば同じとみなされます：

```swift:がという文字.swift
let ga: Character = "\u{304C}"
let kaAndVoicedSoundMark: Character = "\u{304B}\u{3099}"
print(ga == kaAndVoicedSoundMark) // Prints "true"
```

# `CharacterSet`という名前は勘違いさせやすい
`CharacterSet`という名前はあたかも`Character`の集合を表していそうですね。しかも、`init(charactersIn string: String)`なんていうイニシャライザもあります：

```swift:AtoZ.swift
import Foundation
let aToZ = CharacterSet(charactersIn:"ABCDEFGHIJKLMNOPQRSTUVWXYZ")
print(aToZ.contains("A")) // Prints "true"
print(aToZ.contains("!")) // Prints "false"
```

このコードでは、`CharacterSet`が`Character`の集合として振る舞っているようにもみえます。
では、さっきの「が」を使った次のコード例をみてみましょう。

```swift:がセット.swift
import Foundation
let gaSet = CharacterSet(charactersIn:"\u{304B}\u{3099}")
print(gaSet.contains("\u{304C}")) // Prints what...?
```

`"\u{304B}\u{3099}"`は上述した通り「か」＋「゛」です。`"\u{304C}"`は「が」です。`Character`の項でも示した通り、これらは同一とみなされます。即ち、`CharacterSet`が`Character`の集合であれば、結果は`"true"`となるはずです。しかし、このコードを実行して表示されるのは`"false"`です。同じとみなされる文字が含まれているはずなのに、なぜ`"false"`なのでしょうか。

# 実は`CharacterSet`の正体は`UnicodeScalarSet`だった
`CharacterSet`は歴史的な経緯からそういう名前になっているだけで、実は中身はユニコードスカラ値の集合なのです。
`CharacterSet`の別のイニシャライザやメソッドを見てみるとそれが明らかです。

```swift:CharacterSetのイニシャライザやメソッド抜粋
convenience init<S>(_ sequence: S) where S : Sequence, Unicode.Scalar == S.Element
convenience init(arrayLiteral: Unicode.Scalar...)
init(charactersIn range: Range<Unicode.Scalar>)
init(charactersIn range: ClosedRange<Unicode.Scalar>)
func contains(_ member: Unicode.Scalar) -> Bool
@discardableResult mutating func insert(_ character: Unicode.Scalar) -> (inserted: Bool, memberAfterInsert: Unicode.Scalar)
mutating func insert(charactersIn range: ClosedRange<Unicode.Scalar>)
mutating func insert(charactersIn range: Range<Unicode.Scalar>)
@discardableResult mutating func remove(_ character: Unicode.Scalar) -> Unicode.Scalar?
mutating func remove(charactersIn range: ClosedRange<Unicode.Scalar>)
mutating func remove(charactersIn range: Range<Unicode.Scalar>)
@discardableResult mutating func update(with character: Unicode.Scalar) -> Unicode.Scalar?
```

うわぁ…。引数が`Unicode.Scalar`のメソッドはいっぱいあっても、`Character`が引数になっているメソッドは一切ありません。
ちなみに、`var bitmapRepresentation: Data { get }`というプロパティもありますが、それが表すのは`CharacterSet`に含まれる**ユニコードスカラ値を表す`Data`**で、先頭からnビット目が1であれば、ユニコードスカラ値 == nが当該`CharacterSet`に含まれるということになります。

さて、以上を踏まえて、上の"がセット.swift"を見返して見ましょう。
まず`let gaSet = CharacterSet(charactersIn:"\u{304B}\u{3099}")`で作られる`CharacterSet`は「が」という`Character`を持つ集合**ではなく**、`U+304B`と`U+3099`という**2つの`Unicode.Scalar`を含むことを表す集合**です。
そして、次の`gaSet.contains("\u{304C}")`ですが、ここでの`"\u{304C}"`は`Character`ではなく`Unicode.Scalar`です[^ ExpressibleByStringLiteral]。即ち、`gaSet.contains("\u{304C}")`は`U+304C`というユニコードスカラ値が`gaSet`に含まれているかどうかを調べているに過ぎません。となると、当然、`U+304C`は`U+304B`でも`U+3099`でもないので、結果は`"false"`となります。
一方、`gaSet.contains("か")`は`"true"`になります。何故かは分かりますよね？

[^ ExpressibleByStringLiteral]: `Character`も`Unicode.Scalar`も[`ExpressibleByStringLiteral`](https://developer.apple.com/documentation/swift/expressiblebystringliteral)というprotocolに準拠しているので、どちらも`"\u{304C}"`と書けてしまうところがミスリーディングを誘っています。

## 実態に即したコードは…
というわけで、`CharacterSet`は本当は`UnicodeScalarSet`だった訳です。本来は次のようなコードを書くことができれば勘違いは生まれないはずです：

```swift:がセット改.swift
// Swift 4.0現在 実装されていません。
let gaScalars = UnicodeScalarSet(unicodeScalarsIn:"\u{304B}\u{3099}")
print(gaScalars.contains("\u{304C}")) // Prints "false"
let gaCharacters = CharacterSet(charactersIn:"\u{304B}\u{3099}")
print(gaCharacters.contains("\u{304C}")) // Prints "true"
```

## 実は公式でも触れられている
[StringManifestoの"Character and CharacterSet"](https://github.com/apple/swift/blob/swift-4.0-RELEASE/docs/StringManifesto.md#character-and-characterset)に正にそういった問題が書いてあります(YOCKOWによる拙訳)：
> その名前にもかかわらず、`CharacterSet`は現在のところSwiftにおける`UnicodeScalar`型に基づいて機能しています。即ち、`String`で使えるのは`unicode scalar view`を通した時のみです。短期間でこの不調和を解決するのであれば、`CharacterSet`を`UnicodeScalarSet`にリネームすればいいでしょう。長期的には、拡張書記素クラスタ(訳注: Unicodeにおける"一文字"を表すもので、Swiftでいう`Character`)のために同様の機能を提供する`CharacterSet`を導入するのが適切かもしれません。

# おまけ
`CharacterSet`が実は`Unicode.Scalar`の集合だったということが分かったところで、次のコードを見てください：

```swift:国旗.swift
let jpFlag: Character = "🇯🇵" // Flag of Japan
let amFlag: Character = "🇦🇲" // Flag of Armenia
let paFlag: Character = "🇵🇦" // Flag of Panama

let japanAndArmenia: String = "\(jpFlag)\(amFlag)"
let paFlagSet = CharacterSet(charactersIn:"\(paFlag)")

let whichFlag = japanAndArmenia.components(separatedBy:paFlagSet).joined()
print(whichFlag) // Prints "🇯🇲" (Flag of Jamaica)
```

日本とアルメニアとパナマの国旗を文字列操作したらジャマイカの国旗が生まれましたとさ。何故そうなるのか？もうお分かりですね？




----