Skip to content

Instantly share code, notes, and snippets.

@frsyuki
Last active December 14, 2015 03:39
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save frsyuki/5022460 to your computer and use it in GitHub Desktop.
Save frsyuki/5022460 to your computer and use it in GitHub Desktop.

3からの差分:

  • 実装の後方互換性に関する記述を修正
  • 各実装へのリンクを追加
  • 背景にNoteを追加
  • 「UTF-8としてinvalidなバイトシーケンスを含む文字列が保存されていることがある」理由の修正
  • 概要から「バイト列はBinary型で保存する。そうでなければString型で保存する」を削除(背景と型システムの詳細を読んでもらう)
  • weak-string languages のシリアライザのところで、「String型はUTF-8としてinvalidなバイトシーケンスを許容する」の注釈を削除

msgpackの変更案について-4

概要:

  • Binary型を新設する。
  • 現行のRaw型をString型として読み替える。
    • 現行の FixRaw, raw 16, raw 32 は、FixString, string 16, string 32 になる

背景

文字列とバイナリの区別が曖昧な言語が存在する。例えば:

  • 文字列とバイト列を区別する型がそもそも無い言語(PHP, C++, Erlang, OCaml)
  • 文字列を表すためにフラグ等の付加情報が使われるが、文字列なのに付加情報が付与されていないケースが一般的に存在する言語(Ruby, Perl, Python 2)

これらの言語を weak-string languages と呼ぶ。

一方で、区別が明確な dynamically-typed languages も存在する(JavaScript、Objective-C、Python 3)。また、区別が明確な statically-typed languages が存在する(Java、C#、Scala)。これらの言語を strong-string languages と呼ぶ。

Note: ある言語がどのカテゴリに分類されるかは、その使い方に依存する。使い方によって分類が変化する場合もある。

strong-string and dynamically-typed languages が受信者となる オブジェクトのやりとりでは、文字列として送信/保存したデータを文字列として、バイト列として送信/保存したバイト列をバイト列として、透過的に復元したいという要求が存在する。(※この要求は、その他の組み合わせの通信では存在しない)

一方で、weak-string languages では、すべての文字列に対して「これは文字列である」とマーカを付与する、あるいはすべてのバイト列に「これはバイト列である」というマーカを付与する作業は手間がかかる。従ってこれらの言語で書かれたプログラムで、文字列とバイト列が明確に区別できることを期待することは現実的ではない。

そこで、次のメリットを同時に満たす変更を提案する:

  • strong-string languages 同士の通信においては、文字列を文字列として、バイト列をバイト列として透過的に復元できるようにする
  • weak-string languages と strong-string languages が混在した場合のデータ交換では:
    • weak-string languages においてすべてのバイト列にマーカを付ける作業を行えば、strong-string languages で透過的に型を復元できるようにする
    • すべての文字列ではなくすべてのバイト列にマーカを付けるのは、バイト列の方が文字列より数が少なく、その作業の方が簡単だという仮定に基づいている
    • そうでなくても、strong-string languages 側のアプリケーションで適切な実装を行えば、透過的ではないがデータ交換が行えるようにする
  • weak-string languages 同士の通信では、既存のmsgpackとの互換性を維持する

型システムの変更

  • Binary: バイト列
  • String: UTF-8でエンコードされた文字列
    • UTF-8としてinvalidなバイトシーケンスを含む文字列が保存されていることもある。この理由は次の4つ:
      • シリアライズ時にvalidateを行うか否かは実装に依存するため
      • シリアライズ時にvalidateを行うと性能にインパクトがあるため
      • 全言語のシリアライザにUnicodeの正しい取り扱いを実装にさせるには、Unicodeは複雑すぎるため
      • weak-string languages で書かれたプログラムでは、文字列とバイト列が明確に区別できることを期待することは現実的ではなく、結果としてバイト列がStringとして扱われることがあるため
    • invalidなバイトシーケンスを含む文字列を検出した場合の動作は、実装に依存する
      • ただし、invalidなバイトシーケンスを含む文字列をどのように取り扱うかは、アプリケーションが決定するべき仕事である

UTF-8としてinvalidなバイトシーケンスを含むStringを受け取った場合の動作は実装に依存する。例外を発生させて弾く実装を行っても良いし、その場合に限りバイト列型を返すといった実装でも良い。しかし、UTF-8としてinvalidなバイトシーケンスを含むString型オブジェクトが保存されていたとしても、アプリケーションが望めば元のバイト列を取り出せるようにする手段も提供することが、強く推奨される(SHOULD or MUST)。

フォーマットの変更

0xa0-0xbf FixString (0bytes - 31bytes String type)  // changed

0xd5 binary 8 (Binary type)  // new
0xd6 binary 16 (Binary type)  // new
0xd7 binary 32 (Binary type)  // new

0xd8 reserved

0xd9 string 8 (String type)  // new
0xda string 16 (String type)  // changed from raw 16
0xdb string 32 (String type)  // changed from raw 32

実装のガイドライン

strong-string and dynamically-typed languages

  • シリアライザ:

    • バイト列はBinary型として保存する
    • 文字列はString型として保存する
    • ただし、既存の実装との互換性を維持するために、バイト列もString型として保存するオプションを実装しても良い
  • デシリアライザ:

    • String型またはBinary型をデシリアライズしたら、それと分かるオブジェクトを返す
    • String型にUTF-8としてinvalidなバイトシーケンスが含まれていた場合に、アプリケーションがそれらをハンドリングできるようにする機能を提供するべきである(SHOULD)
    • この実装方法は特に規定しない。次のような方法が考えられる:
      • invalidなバイトシーケンスを発見したら、そのオリジナルのバイト列をフィールドに持つオブジェクトのインスタンスを返す
      • invalidなバイトシーケンスを含むことができる文字列クラスを組み込み型とは別に作成し、invalidなバイトシーケンスが含まれるか否かに関わらず、常にそれを返すモードを実装する
      • invalidなバイトシーケンスを発見したら指定されていたコールバック関数を呼び出し、その関数の返値を返す

weak-string languages

  • シリアライザ:
    • バイト列か文字列かが自明でなければ、String型として保存する
    • UTF-8のvalidationは行わなくてよい
    • ユーザーが明示的に「これはバイト列だ」とヒントを設定したオブジェクトを受け取った場合、それはBinary型として保存するべきである(SHOULD)
  • デシリアライザ:
    • デフォルトの動作でString型のvalidationを行って弾くべきではない(SHOULD NOT)
      • なぜなら、アプリケーションがinvalidなバイトシーケンスを含むStringの扱いを決めるべきだから
    • オプションを有効にすればString型でvalidationを機能を実装をしてもよい(MAY)
    • Binary型を受け取った場合、何らかのフラグが立った(String型とは区別できる情報を含む)オブジェクトを返すオプションを実装しても良い(MAY)
      • この挙動は、MessagePackを入力として、MessagePackを出力するような、中間処理を行うツールで、出力先でも型情報を維持しなければならないケースで必要になる
      • 実装の方法には、例えばPHPにおいては、特定のオプションがonであったら、Binary型を受け取った場合に、バイト列をフィールドとして所有するMessagePackBinaryクラスのインスタンスを返す方法ある

既存の実装の互換性について

  • マイナーバージョンアップで、新設されるstring 8, binary 8, binary 16, binary 32をバイト列として返す実装をリリースする
    • この時点で、新しいシリアライザとの前方互換性が達成される
    • おそらく、それほど難しくない
  • メジャーバージョンアップで、バイト列を新設Binary型でシリアライズする実装をリリースする
    • 同時に、区別が厳格な環境のシリアライザにおいて、バイト列もStringとして保存し、また string 8 を使用しないオプションを提供する
      • これは、新しいシリアライザに切り替えた場合でも、書き出されるバイト列が変化しないようにするために存在する
      • 既存の実装と互換性を維持するには、メジャーバージョンアップすると同時に、このオプションをONにする
    • また、String型とBinary型の両方をバイト列として返すモードを提供する
      • これは、新しいデシリアライザに切り替えた場合でも、ソースコードの互換性を維持するために存在する
    • リリースノートなどで、どのオプションを有効にすれば後方互換性を維持できるかを明示するべきである

募集中のアイディア

「用語」で触れられている言語の種類を増やしたい。コメント求む。

この変更後のMessagePackの呼び方。

  • 案1:現行の仕様をMessagePack 0.9 と呼び、新しい仕様を MessagePack 1.0 と呼ぶ
  • 案2:現行の仕様をMessagePack 1.0 と呼び、新しい仕様を MessagePack 1.1 と呼ぶ
  • 案3:現行の仕様をMessagePack 1.0 と呼び、新しい仕様を MessagePack 2.0 と呼ぶ
  • 案4:バージョン番号は付けず、単に新仕様をMessagePackと呼ぶ

各種実装に関するリンク

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment