XMLの論考: YAMLはXMLに改良を加える

YAML Ain't Markup Language (YAMLはマークアップ言語ではない)

この記事では、DavidがYAMLを紹介します。これは、人間にとって読みやすいデータ直列化フォーマットであり、動的なプログラム言語で使用されるデータ型をエンコードするのにうってつけです。XMLと対照的に、YAMLが使用する構造の標識は、クリーンかつ最小限のものです。これは、ネストされた要素を字下げするという方法に主に頼っていることによります。より重要なこととして、YAMLは構文が優れており、多くのタスクの場合に、YAMLのデータ構造と「自然な」データ構造との間のセマンティックな適合関係は、ずっとぴったりしたものとなっています。

David Mertz, Ph.D, Author, Gnosis Software, Inc.

Photo of David MertzDavid Mertz 氏は多くの分野で活躍しています。ソフトウェア開発や、それについて著述もしています。その他、学術政策理念について分野を問わず、関係する雑誌に記事も書いています。かなり以前には、超限集合論、ロジック、モデル理論などを研究していました。その後、労働組合組織者として活動していました。そして、David Mertz 氏自身は人生の半ばにもまだ達していないと思っているので、これから何かほかの仕事をするかもしれません。



2002年 10月 01日

筆者の想像では、読者が最初に思い浮かべる質問は、「なぜYAML という名前なのだろう?」というものに違いありません。気取って、"Yet Another XXX (もう1つのXXX)" を意味する "YA*" という頭字語を採用してきたツールはたくさんあります。オープン・ソースのウィットの世界において、YAMLは、その暗黙の頭字語を避け、代わりに再帰的な "YAML Ain't Markup Language (YAMLはマークアップ言語ではない)" に定着しています。しかし、これにはいくらかの意味があります。つまり、YAMLはマークアップ言語が行うことを行いますが、マークアップ を何も必要としないのです。

YAMLは、汎用性の点でXMLに劣るものの、XMLよりずっと読みやすく、編集、修正、および作成がずっと容易です。つまり、XMLで表現できるものなら何でも、YAMLで (ほとんどの場合、よりコンパクトに) 表現できるということです。名前空間は、特別な弁解が必要な1つの事例となっています。名前空間をYAMLに「ボルトで留める」ことはできますが、現在のところ、名前空間は仕様に含まれていません。

このコラムで筆者が頻繁に取り上げてきたXMLへの批判の1つは (これを強調するのは決して筆者一人ではありません)、XMLはフォーカスが十分に絞られていないということです。古典的かつ委員会主導の巨獣であるXMLは、文書フォーマットであろうとし、データ・フォーマットであろうとし、メッセージ・パケット・フォーマットであろうとし、セキュアなRPCチャネルであろうとし (SOAP)、またオブジェクト・データベースであろうとしています。その上、XMLは、アクセスおよび操作のスタイルごとにAPIを上乗せしています。つまり、DOM、SAX、XSLT、XPATH、JDOM、およびより一般性の低いインターフェース層が何ダースもあります (筆者自身も、gnosis.xml.picklegnosis.xml.objectify、およびgnosis.xml.validity パッケージでいくらか貢献してきました)。注目すべきこととして、XMLは上記の事柄をすべて行います。しかし、がっかりさせられることに、XMLはこのうちどれも特別上手に行うわけではありません。

YAMLのフォーカスは、より狭い範囲、つまり、動的なプログラム言語 (Perl、Python、Ruby、および程度は限られるもののJavaプログラミングなど) に見られるデータ構造やデータ型をきれいに表現することに絞られています。現時点で、上記の言語用のバインディング/ライブラリーが存在しています。他の多くの言語のデータ型もYAMLと仲良くできる でしょうが、ライブラリーはまだ作成されていません。そうした言語としては、Lisp/Scheme、Rebol、Smalltalk、xBase、およびAWKがあります。動的な度合いの低い言語は、YAMLとそれほど良く調和しないかもしれません。YAMLの構文は、Perlのコンテキストに応じた型付け、Pythonの字下げ構造、およびMIME標準のいくつかの規則を組み合わせたものです。Pythonがしばしば実行可能な疑似コード として称賛されるのとほぼ同様、YAMLの明確な構文は、クラスやワークグループに対してデータ構造を形式ばらないで説明するのに使用するであろうものに非常に近いものとなっています。

アプリケーションをスケッチする

さまざまなフォーマットのコードを見てみれば、YAMLを使用したいと思わせる理由が簡単に分かります。今回の記事では、データの保管と伝送という要件を持つ、小さなアプリケーションを作成しているものと想定します。筆者のお気に入りのプロジェクトは、この記事の執筆中に開催された "Brains in Bahrain" チェス・トーナメントからインスピレーションを受けたものです。このトーナメントは、FISAのチェス世界チャンピオンと、最高ランクのコンピューター・プレーヤーの間で争われるものです (データの詳細は話半分に受け取ってください)。チェス・クラブの活動をトラッキングするプログラムを作成する場合、以下のPerlコードによって記述されるデータ構造を使用するかもしれません。

リスト1. Perlで記述したチェス・クラブのデータ構造
$players = {
  'Vladimir Kramnik' => {'status'=>'GM', 'rating'=>'2700'},
  'Deep Fritz' =>  {'status'=>'Computer','rating'=>'2700'},
  'David Mertz' => {'status'=>'Amateur', 'rating'=>'1400'},
};
$club = {
  '_players' => $players,
  'matches' => [
    {'Date' => '2002-10-04',
     'White' => $players->{'Deep Fritz'},
     'Black' => $players->{'Vladimir Kramnik'},
     'Result' => 'Draw' },
    {'Date' => '2002-10-06',
     'White' => $players->{'Vladimir Kramnik'},
     'Black' => $players->{'Deep Fritz'},
     'Result' => 'White' }
  ]
};

Pythonは、上記のPerlのコードに似ています。

リスト2. Pythonで記述したチェス・クラブのデータ構造
ts = yaml.timestamp# or mx.DateTime or othr date class
players = {
'Vladimir Kramnik':  {'status':'GM','rating':2700},
'Deep Fritz':        {'status':'Computer','rating':2700},
'David Mertz':       {'status':'Amateur','rating':1400},
  }
matches = [
  {'Date':      ts('2002-10-04'),
'White':     players['Deep Fritz'],
'Black':     players['Vladimir Kramnik'],
'Result':'Draw' },
  {'Date':      ts('2002-10-06'),
'White':     players['Vladimir Kramnik'],
'Black':     players['Deep Fritz'],
'Result':'White' }
  ]
club = {'_players':players,'matches':matches}

他の動的なプログラム言語でも、同様のデータ構造の記述を使用することでしょう。基本的に言って、これはいくつかの再帰的なレベルの辞書とリストの両方が含まれた、トップレベルの辞書/マッピング/ハッシュです。これはまた、ネストされた要素がお互いを参照することも許可しています。

チェス・クラブを管理するアプリケーションであるなら、おそらく、追加の対局を記録すること、プレーヤーをクラブに追加/削除すること、あるいは対局に基づいてレーティングを更新することなどのタスクを実行できることでしょう。加えて、そのようなアプリケーションは、データのスナップショットを記録 するだけでなく、同じデータ・モデルを処理する他のアプリケーション (他の言語による) とデータを共有 することも望むかもしれません。さらに、アプリケーションの外で、手作業でデータを簡単に修正できれば望ましいことでしょう。データ指向のアプリケーションを開発したことのある人なら、あるいは組織の記録を保守したことのある人なら誰でも、基礎となるデータ構造の「中身をつつく」ことができれば便利だということを知っています。


表現を選択する

おそらく読者は、筆者が議論の下準備を終えたことにすでにお気付きでしょう。ここでYAMLが正しい解決策として急降下するのです。しかし、筆者は設定が不当なものとは思いません。データを構造化した方法は、データがそれ自身を提示するときにしばしば用いる方法と、少なくとも大体において、良く似ています。上記のマップ、リスト、参照、およびデータ型は、それぞれが最も自然に見えるような場所で正しく用いるようにしました。また、根底となる問題も、論点を示すのに十分な複雑さでありながら、1つの記事に収まる程度に簡単なものとして選択したものに過ぎません。

すべてのチェス・クラブ・アプリケーションに特定のプログラム言語を (ことによると特定のバージョンも) 要求するつもりなら、大部分の言語には直列化の優れた機能が、組み込みライブラリーまたは共通ライブラリーの形で備わっています。

  • PythonにはcPicklegnosis.xml.pickle、およびpprint があります
  • PerlにはData::DumperData::Denter、およびData:DumpXML があります
  • RubyにはMarshalXmlSerialization があります
  • Java言語にはjava.io.Serializableorg.apache.xml.serialize.XMLSerializer、および他のさまざまなライブラリーがあります

名前が示しているとおり、上記のライブラリーの一部はXMLを作成します。とは言え、XMLが言語同士の間で容易に転送可能であるとはとても言えません。

それに加えて、このチェス・クラブのデータを表現するためにXMLを使用することには、いくつかの一般的なセマンティクス上の問題があります。XMLでは、要素の属性については順序なしのマッピングの概念があるものの、ネストされた要素については厳密な順序付けを行います。当然、特定のアプリケーションは順序付けの情報の一部を無視 できますが、XMLの情報モデル は、(時には見せかけだけの) 順序付けを行うことの重要性を常に主張しています。たとえば、対局は特定の順序 (日付順) に収まると考えられますが、プレーヤーは本質的に順序付けの対象ではありません。(もちろん、レーティングや入会日付などの順序を押し付ける ことはできるかもしれません。)問題は、すべてのアプリケーションについて、押し付けられた順序付けが見せかけだけのものならいつでもこれを除去 し、順序付けが重要な場合はこれを保持 するためのカスタム・プログラミングが必要になるということです。

XML-RPC、SOAP、gnosis.xml.pickle、およびさまざまな言語のXMLシリアライザー・ライブラリーでは、マッピングを表現するために汎用的なアプローチを採用しています。上記のすべては、基本原則として、順序なしの対を表すには<key> および<val> (または類似のタグ) を (半ば冗長なものとして) 使用し、順序付けされた項目を表すには別のコンテナー要素を使用します。この原則では、XML情報モデルの一部を除去するために、いくつかの層が追加されます。

リスト3. 順序付きおよび順序なし集合のXML-RPCモデル
>>> import xmlrpclib
>>> print xmlrpclib.dumps(({'this':'that',
...                         'spam':('eggs','toast')},))
<params>
<param>
<value><struct>
<member>
<name>this</name>
<value><string>that</string></value>
</member>
<member>
<name>spam</name>
<value><array><data>
<value><string>eggs</string></value>
<value><string>toast</string></value>
</data></array></value>
</member>
</struct></value>
</param>
</params>

XML-RPCの場合はさらにいくつかの作為が施されていますが (オブジェクト全体を1項目のタプルにラップしなければならないことなど)、こうした事柄は小さな問題に過ぎません。「ネイティブ」のデータ・モデルとXMLデータ・モデルの適合関係がぎこちないことは、ここで言及するあらゆるXML直列化フォーマットについても、同様に明白であると言えます。


XMLを試みる

このチェス・クラブのデータをXMLで表現することに伴って生じる問題は、少なくとも2つあります。最初の、そしてより単純な問題は、理論的に言って、最良のXML表現とは一体どのようなものかということです。そのために、試みに次のようなものを最良のXMLとして提案したいと思います。

リスト4. チェス・クラブ・データの最適のXML記述
<?xml version="1.0"?>
<club>
  <players>
    <player id="kramnik"
            name="Vladimir Kramnik"
            rating="2700"
            status="GM" />
    <player id="fritz"
            name="Deep Fritz"
            rating="2700"
            status="Computer" />
    <player id="mertz"
            name="David Mertz"
            rating="1400"
            status="Amateur" />
  </players>
  <matches>
    <match>
        <Date>2002-10-04</Date>
        <White refid="fritz" />
        <Black refid="kramnik" />
        <Result>Draw</Result>
    </match>
    <match>
        <Date>2002-10-06</Date>
        <White refid="kramnik" />
        <Black refid="fritz" />
        <Result>White</Result>
    </match>
  </matches>
</club>

上記のXMLデータ表現は、まったく明確です。PerlおよびPythonの例で挙げたネイティブのデータ記述に比べてまったく冗長なところがありません。リスト5 のYAMLの記述と比べても同じことが言えます。テキスト・エディターのような汎用ツールを使って文書を修正するのもそれほど困難ではありません。(実際、最初はまさにこのようにしてXMLを作成しました。)

セマンティックに言って、上記のXMLの提案には、先に論じた問題がすべて含まれています。プレーヤーは、実際には順序付けの対象ではないのに、順序付けされているように見えます。また、プレーヤーのリストの後に対局リストが置かれていますが、実際にはそのような概念上の順序が意図されているわけではありません。プレーヤーの属性には、希望どおりに順序がありませんが (XML属性であるため)、対局の「属性」はXML属性には収まらないので、人工的な順序が押し付けられています。

より重要な問題は、この最適なXMLフォーマットを実際に読み書き することに伴って生じます。一般的なXML APIの中には、この操作を自動化することに近づいているものさえ1つもありません。たとえば、SAXリーダーは、さまざまな「プレーヤー」と「対局」イベントを探して、関係するネスト辞書またはリストに手動で追加することはできるかもしれませんが、このアプローチはもろいものであり、開発中にデータ構造にほんのわずかな変更が加えられただけでも、プログラムのやり直しが必要です。DOMツリーをたどることにも同様の問題があります。JDOM またはREXML などのカスタムAPIのどちらも、あまり助けにはなりません。gnosis.xml.objectify は、ネイティブ・オブジェクトを自動生成する点で大変良い仕事を行いますが、XMLを読み取ることしかできず、これを書き戻すことはできません。当然のことながら、書き込むことは、それに伴うあらゆるもろさがあるとは言え、読み取ることと対になるものです。


YAMLが救助に駆け付ける

YAMLフォーマットは、動的な言語のデータ構造と、直感的により良く適合しています。見た目も、より優れています。ここに、同じチェス・クラブのデータをYAMLで表現したものを示します。

リスト5. YAMLで記述したチェス・クラブのデータ
---
players:
  Vladimir Kramnik: &kramnik
    rating: 2700
    status: GM
  Deep Fritz: &fritz
    rating: 2700
    status: Computer
  David Mertz: &mertz
    rating: 1400
    status: Amateur
matches:
  -
    Date: 2002-10-04
    White: *fritz
    Black: *kramnik
    Result: Draw
  -
    Date: 2002-10-06
    White: *kramnik
    Black: *fritz
    Result: White

このフォーマットには、優れた点がたくさんあります。YAML Webサイトには正確な仕様書が掲載されていますが (参考文献を参照)、この簡単なサンプルを見れば、基本的な要素の概念をかなり正しく理解することができます。仕様には、(複数) 段落のストリングを組み込むための、直観的な方法も含まれています。YAMLは簡潔なものですが、それでもなお読みやすいものです。その上、引用符の使用は最低限のもので、データ型はパターンから推測されます (たとえば、日付のように思えるものは、明示的にストリングとして引用符で囲まない限り、タイム・スタンプとして扱われます)。あらゆる名前付きのターゲットへの参照を使用することができます。さらに、意味深いこととして、YAMLは順序付きの集合と連想集合の区別を保ちます。追加のボーナスとして、YAMLはテキスト・エディターで非常に簡単に編集できます。

実のところ、上記のセマンティック上および構文上の利点は、このアプリケーションでYAMLを使用する最も大きな理由ではありません。実は、最良の点は、サポートされている言語すべてに渡ってインターフェースが一様であるということです。上記のYAMLデータ・ファイルの読み取り、操作、および書き込みは、以下のようにして非常に簡単に行うことができます。

リスト6. PythonでYAMLデータ・ソースにアクセスする

import yaml
club = yaml.loadFile('club.yml').next()
# ...manipulate the 'club' data structure...
club_yamlstr = yaml.dump(club)
# ...do something w/ formatted YAML in club_yamlstr...

上の例では.next() メソッドを使用しています。なぜなら、YAMLのテキストには、それぞれ--- で区切られた、複数のストリームが含まれている場合があるからです。偶然にも、club のデータ構造は、先に純粋なPythonの定義で定義したデータ構造とまったく 同じです。

Perl (またはRubyあるいはJavaプログラミング) でも、ステップはほとんど同じです。

リスト7. PerlでYAMLデータ・ソースにアクセスする

use YAML ();
my $club = YAML::LoadFile( 'club.yml');
my $club_yamlstr = YAML::Dump($club);

YAMLのデータ構造とネイティブのデータ構造との間の行き来は自由…、と言うか、それに非常に近いものです。小さな欠点を2つ見つけました。

  • 参照の名前 (たとえば "*kramnik") が失われて、単なる番号 (たとえば、"*1") になる。
  • ターゲットが、最初に現れる位置では、必ず完全にスペルアウトされる。

美的な観点から、プレーヤーの詳細が "players" セクションに現れるのが筆者の好みですが、順序なしの辞書の場合、これは保証されません (Perl/Pythonのサンプルで_player を使用するのが、物事を強制的に操作するためのうまい方法です)。


何を意味するか

ここで扱っていないYAMLの機能はたくさんあります。公式の仕様書は、(たいていの仕様書と同様) いくらか読みにくいものですが、優れたものです。たとえば、既存のYAMLライブラリーには、XMLとYAMLの間で行き来するための十分な (優秀とはいきませんが) 変換ツールが付属します。また、YPATHと呼ばれる技術 (XPATHのYAML版) のサポートもあります。

この紹介記事の意図は、YAMLがXMLより優れたオブジェクト直列化フォーマットを提供するいくつかの状況を提案することでした。筆者の心の中では、XMLが常にデータ表現の最良の選択であるとは限りません。そのことが明白ではない多くの状況においてさえもです。

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML
ArticleID=241021
ArticleTitle=XMLの論考: YAMLはXMLに改良を加える
publish-date=10012002