書いて覚えるSwift入門

第41回Swiftyなデータ処理

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)するような設計にしてあります。この点はSwiftyJSONswift-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の最大の美点なのですが、ここまで書いたところで誌面が尽きたようです。次回は具体的にどのようにすればそうできるのかをみていくことにします。

Software Design

本誌最新号をチェック!
Software Design 2022年9月号

2022年8月18日発売
B5判/192ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    MySQL アプリ開発者の必修5科目
    不意なトラブルに困らないためのRDB基礎知識
  • 第2特集
    「知りたい」「使いたい」「発信したい」をかなえる
    OSSソースコードリーディングのススメ
  • 特別企画
    企業のシステムを支えるOSとエコシステムの全貌
    [特別企画]Red Hat Enterprise Linux 9最新ガイド
  • 短期連載
    今さら聞けないSSH
    [前編]リモートログインとコマンドの実行
  • 短期連載
    MySQLで学ぶ文字コード
    [最終回]文字コードのハマりどころTips集
  • 短期連載
    新生「Ansible」徹底解説
    [4]Playbookの実行環境(基礎編)

おすすめ記事

記事・ニュース一覧