APFSの秘密を解く
前回予告で今回は「総称型に焦点を当てる」と宣言しましたが、その前にやっておくべきことがありました。Swiftにおける文字の扱いです。なぜ今なのか。こちらをご覧ください(図1)。
なぜこうなってしまうのか。その秘密はファイル名にあります。
Perlで書かれたリスト1の検証コードでHFS+の空ディレクトリを指定して実行すると、こうなります。
ところがAPFSだとこうなるのです。
いったい何が起きているのでしょう?
HFS+では\x{304b}\x{3070}\x{3093}.txt
と\x{304b}\x{306f}\x{3099}\x{3093}.txt
は同じファイル名として扱われていますが、APFSでは異なるファイル名として扱われているのです。
そしてSwiftも、\u{304b}\u{3070}\u{3093}
と\u{304b}\u{306f}\u{3099}\u{3093}
を等しいとみなしているのです。
文字列が単なるバイト列だった前世紀には驚きの結果ですが、Unicodeが普及した今世紀にはむしろUnicode正規化を前提に比較するというのはUnicodeの理念からすると実は正しい。しかしUnicode的に「正しく」==
を実装しているのは、筆者の知る限り現時点においてSwiftぐらいのものでしょう。
SwiftのUnicode原理主義ぶりは、==
にとどまりません。先のPerlスクリプトをFoundationを使って移植した検証コード(リスト2)では、APFSでもHFS+と同じ結果が出るのです。
ただしそうなるのはmacOSの場合。Linux上ではPerlと同じように振る舞います。
てんでんばらばらちんぷんかんぷんまとまらない?
なんともややこしそうですが、ベストプラクティスはすでに存在します。NFC(Normalization Form Canonical Compression)にしてしまうのです。
・IMなどで文字列入力した場合、ほぼ100%NFCになる。つまりソースコード中の
Unicode文字列は当初からNFC
・NFCのほうがバイト数が少ない
・HFS+は双方受け付けるので、あえてNFDにする必要はない
SwiftでString
をNFCにするには、import Foundation
してから.precomposedStringWithCanonicalMapping
にアクセスするだけです。Swift 3.1ならLinuxもサポートしています。こんな長いの覚えられないというのであれば、次のようにしても良いでしょう。
これで"\u{304b}\u{306f}\u{3099}\u{3093}".nfc
とすればかばんは3文字で収まります。
もうひとつ念のために、同等な比較に加えて同値な比較も用意しておくのもよいでしょう。たとえば===
を次のように定義しておくのです。
そうすると、次のようになります。
Perlに慣れた筆者から見ると、Swiftの文字列処理はまだまだひよっこもいいところなのですが、はじめからUnicodeをきちんと扱っているのはとてもうらやましい。Our goal is to be better at string processing than Perlという公約が実現するかどうかはさておき、PerlやPHPやPythonやRubyはUnicodeが後付けなおかげで今でも苦労しているのですから。
次号予告
ところで==は、どんな型にも必ず存在するわけではありません。
ここでa == b
としても、error: binary ope rator '==' cannot be applied to two 'Any' operands
と怒られてしまいます。Equatable
プロトコルに準拠している型だけが等しさを享受できるのです。
プロトコル? 準拠? 次回はいよいよSwift最大の特長であるプロトコルを、総称型と絡めつつ紹介します。
- 第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識
- 第2特集
「知りたい」「使いたい」「発信したい」をかなえる
OSSソースコードリーディングのススメ
- 特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画]Red Hat Enterprise Linux 9最新ガイド
- 短期連載
今さら聞けないSSH
[前編]リモートログインとコマンドの実行
- 短期連載
MySQLで学ぶ文字コード
[最終回]文字コードのハマりどころTips集
- 短期連載
新生「Ansible」徹底解説
[4]Playbookの実行環境(基礎編)