swift-sion
前回 予告どおり、今回はswift-sion を題材にSwiftyなデータ処理とは何かを考察していきます。
to type or not to type, that's the question.
前回記事より、SION の実装はさらに増えて合計5種5言語になりました。
ここで言う「実装」とは、SIONをシリアライズ(serialize) /デシリアライズ(deserialize)できるという意味ですが、どのようにそうしているのかというのは、それぞれの言語ごとにかなり異なります。一番異なるのは、言語に組込みの型をそのまま使うか、そのための型を別途用意するかでしょう。js-sionは前者、swift-sionは後者です。双方とも筆者が作者なのですが、おかげで責任を持ってなぜそうしたのかを説明できます。
何のためにシリアライズするのか
たとえばSIONのDictionary
。js-sionでは、String:SION
の場合はObject
、それ以外の場合はMap
にデシリアライズされます。どちらもECMAScript 6には標準装備されている型です。それに対し、swift-sionではこの場合も含め、SIONで表現されたバイト列はすべてSIONという、標準装備されていない専用の型にデシリアライズされます。
一見、データ交換用のシリアライゼーションのために専用の型を用意するというのはいささか本末転倒の感じもします。なんのためにデシリアライズするかといえば、その言語の中でそのデータを使うためであり、だとしたらその言語に自然に備わった型にそのままデシリアライズするのが最も使いやすいはずです。事実、JSON はECMAScriptのリテラルそのまま(literally literal!)であり、黎明期においてデシリアライズはeval()
でなされました。今ではJSON.parse()
という専用関数が組み込まれましたが、その結果はArray
なりObject
なり、ECMAScriptの標準型であることに変わりありません。
しかし、言語をまたがってデータ交換がなされるようになると、それ以外の言語で型の違いが問題になってきます。たとえばCやPerlにはBool
に相当する型が不在で、数値が0
ならfalse
、それ以外はtrue
という扱いです。これがPerlになると0 but true
などという場合が出てきてさらにややこしい。こうした問題をどう解決するか。
必要は発明の母
「なければ作ればいい」 。これは型にも当てはまります。Perlに標準装備のJSON::PP
では、JSON::PP::Boolean
という型―Perlにおいてはクラス―を用意してこの問題を解決しています。ただし用意されているのはあくまでもJSON
を表現するのに足りないデータ型だけで、すべてのJSONを過不足なく受け入れるJSON
型というのは用意されていません。これはある意味当然で、Perlでは、
use JSON::PP;
my $array_ref = json_decode('[0,1]');
my $hash_ref = json_decode('{"one":1}')
の$array_ref
は普通の配列リファレンス、$hash_ref
は普通のハッシュ(辞書)リファレンスであるのがデータ交換フォーマットとして期待される振る舞いで、オレクラスを押し付けられてもユーザは困ってしまいます。JSON::PP::Boolean
はある意味必要悪であり、実際数値や文字列としても評価可能で、"" . decode_json("true")
は"1"
となります。
シリアライズする2つの理由
それでは、なぜswift-sionではSION表現が過不足なくデシリアライズできるSION
型を用意したのでしょう。JSON同様、ある言語のリテラルをほぼそのまま転用したのであればなおのこと。
理由は2つ。1つはSwiftにeval()
が存在しないこと。同様のことは不可能ではありませんが、そのためにはswiftc
が必要になります。これはSwiftが根底で「スクリプト言語」ではない傍証ともみなせます。「 スクリプト言語」というのはあいまいな言葉ですが、利用にあたってランタイム(runtime)という名のコンパイラが必ず備わっているという定義を筆者は採用しています。Swiftはスクリプト言語のように使うこともできますが、SwiftでコンパイルされたアプリケーションはSwiftの実行環境がなくても実行できるというのが大前提になります。である以上、SIONをシリアライズ/デシリアライズするのに過不足ない型を用意するのがもっとも自然かつ楽だということになります。
もう1つは、Swiftはユーザ定義の型をあたかも標準装備の型のように扱えるようにするのが容易な言語だから。演算子(operators)に添字(subscript)にコンピューテッド・プロパティ、そしてExpressiveBy*Literal
プロトコル……、これらの条件がそろって初めてSION
型が成立するのです。
import SION
御託はこれくらいにして、swift-sionを使ってみましょう。GitHubのREADME.md
どおりに環境を整えたら、
import SION
とするだけです。これだけで、
var sion:SION = [
"nil": nil,
"bool": true,
"int": -42,
"double": 42.195,
"string": "漢字、カタカナ、ひらがなの入った
string",
"array": [nil, true, 1, 1.0, "one", [1],
["one":1.0]],
"dictionary": [
"nil":nil, "bool":false, "int":0, "double":0.0, "string":"","array":[], "object":[:]
],
"url":"https://github.com/dankogai/"
]
という具合にふつうにSIONをリテラルとして受け付けます。試しに:SION
を取ってみてください。どうなりましたか?
もちろん、文字列をはじめ、ほかのデータソースからの初期化、つまりデシリアライゼーションもサポートしています。
SION(string:sionStr)
SION(json:jsonStr)
SION(jsonUrlString:"https://api.github.com")
SION(propertyList:plistXML.data(using:.utf8)!, format:.xml)
SION(msgPack:msgData)
デシリアライゼーションはさらに簡単で、SION型の文字列表現そのものがSIONフォーマットです。sion.description
でも"\ (sion)"
でもいいですし、そのままprint(sion)
してもいいでしょう。
直感的なのは、シリアライズ/デシリアライズだけではありません。データへのアクセスもそうなっています。たとえば空のSION配列は、
var sa = SION([])
で、
sa[0] = nil
sa[1] = true
sa[2] = 1
という具合にあたかも組込みの配列のように値を代入できますし、辞書も同様にSwiftで、
var sd = SION([:])
sd["nil"] = nil
sd["bool"] = false
sd["int"] = 0
とできます。1点「Swiftらしくない」のは、添字が不在の場合。Swiftの標準のArray
では範囲外へのアクセスはfatalError
になりますし、Dictionary
で存在しないキーはnil
を返しますが、swift-sionでは.Error()
という特別な値を用意し、以降のアクセスはそれが伝播(propagate)するような設計にしてあります。この点はSwiftyJSON やswift-sion の元ともなったswift-jsonに合わせました。
もう1点注意を要するのは、添字でアクセスされた要素もまたSION型である、というよりSION型に包まれていること。上記の例では、
sa[1] = true // SION.Bool(true)
sa[2] = 1 // SION.Int(1)
であり、アンラップ(unwrap)するには、
sa[1].bool
sa[2].int
とします。なおこれらのコンピューテッド・プロパティはgetterであるだけではなくsetterでもあるので、Swiftでは次のような操作も受け付けます。
sion[2].int! += 1 // now 2
sion[3].double! *= 0.5 // now 0.5
sion[4].string!.removeLast() // now "on"
sion[5].array!.append(2) // now [1, 2]
sion[6].dictionary!["two"] = 2 // now
["one":1,"two":2]
次回予告
swift-sionやSwiftyJSONのような「組込み型のようなユーザ定義型」が簡潔に書けるのは、筆者にとってSwiftの最大の美点なのですが、ここまで書いたところで誌面が尽きたようです。次回は具体的にどのようにすればそうできるのかをみていくことにします。
第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識
第2特集
「知りたい」「使いたい」「発信したい」をかなえる
OSSソースコードリーディングのススメ
特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画]Red Hat Enterprise Linux 9最新ガイド
短期連載
今さら聞けないSSH
[前編]リモートログインとコマンドの実行
短期連載
MySQLで学ぶ文字コード
[最終回]文字コードのハマりどころTips集
短期連載
新生「Ansible」徹底解説
[4]Playbookの実行環境(基礎編)