- 米国国税庁が新しい Tax Withholding Estimator(TWE) をオープンソースとして公開し、米国税法を XMLベースの宣言的仕様としてモデル化した構造 が中核となる設計原則
- TWEの税額計算ロジックは Fact Graph というロジックエンジン上に構築されており、各税項目をXMLで定義された「ファクト(Fact)」の依存関係グラフとして表現
- JavaScriptのような 命令型言語 で税務ロジックを実装すると、実行順序の管理、中間値の消失、実装詳細の露出といった問題が生じるため、宣言的アプローチが必須
- JSONは任意の入れ子式の処理に不向きで、XMLはタグ自体がオブジェクトの種類を表すため、DSL構築により適している
- XMLはXPathなどの 成熟したツールエコシステム を無料で活用できるため、クロスプラットフォームな宣言的仕様の最も費用対効果の高い選択肢
Fact Graph: XMLで表現した米国税法
- 米国国税庁 IRS が公開した Tax Withholding Estimator(TWE) は、納税者が 所得と控除額を入力して税額と源泉徴収額を推定 するツール
- プロジェクトは オープンソース として公開されており、一般からの貢献も受け付けている
- TWEは 2つのXML設定 から生成される静的サイトで、1つ目は米国税法を表現した Fact Dictionary
- Fact Graph は IRS Direct File プロジェクトで元々構築されたロジックエンジンで、納税者の納税義務と源泉徴収を Fact Dictionary で定義されたファクトに基づいて計算
- 各ファクトはXMLで定義され、たとえば
/totalOwed は /totalTax から /totalPayments を引く派生(Derived)ファクトとして表現
- 「総納付額(total owed)」は、所得に対する総税額(total tax)と既に納付した金額(total payments)の差
- 還付可能クレジット(refundable credits) は税額残高をマイナスにできる税額控除で、Earned Income Credit、Child Tax Credit、American Opportunity Credit などを
<Add> で合算
- 非還付クレジット(non-refundable credits) は税負担を0までしか減らせず、
<GreaterOf> 演算子で 0 と(暫定税額 - 非還付クレジット)の大きい方を選択
- ユーザー入力値には
<Derived> の代わりに <Writable> タグを使い、<Dollar/>、<Boolean/> などで値の型を指定
- ファクト同士が相互に依存し、最終的な税額を導く グラフ構造
税務ロジックに宣言的仕様が必要な理由
- JavaScriptで同じ計算を書くと
const totalOwed = totalTax - totalPayments のように簡潔だが、これは 命令型(imperative) 方式で順次実行され、その後中間段階が失われる
- 依存関係が深くなると 実行順序の問題 が発生する。
getInput() のようなユーザー入力関数が以後の計算をすべてブロックし、配偶者の有無などに応じて質問自体も変える必要がある
- Social Security の所得合算ロジックでは
map/reduce のようなJavaScriptの実装詳細が露出する一方、XMLの <CollectionSum> は 税務上の数理概念そのもの を表現する
<Dependency path="/socialSecuritySources/*/totalFederalTaxesPaid"/> により、コレクション内の項目を合算
- Fact Dictionary は 宣言的(declarative) 方式で、計算の具体的な実行手順や順序を記述せず、名前付き計算と依存関係だけを記述すれば、エンジンが自動的に実行方法を決定
- 宣言的な税務モデルの最も重要な利点は 監査可能性(auditability) と 内部調査(introspection) であり、プログラムに「この数字はどう導かれたのか」と問いかけられること
- 命令型プログラムでは中間値はすでに破棄されており、ログやデバッガでしか確認できないため、米国税法のように数百の中間計算がある場合は拡張不可能
- Fact Graph の原著者 Chris Given によれば、Fact Graph は「質問しなかった項目が税務申告結果を変えておらず、受けられるあらゆる税制上の恩恵を受けていることを 証明する手段 」
- TurboTax を作った Intuit も同じ結論に達し、2020年に「Tax Knowledge Graph」ホワイトペーパーを発表したが、実装は非公開
- IRS Fact Graph は オープンソースかつパブリックドメイン で、誰でも研究、共有、拡張できる
XMLがJSONよりDSLにずっと適している理由
- 税法の宣言的データ表現形式としてJSONを試すと、任意にネストした式の処理が非常に扱いにくい
- JSONの複合データ構造は実質的にオブジェクトしかないため、すべての子オブジェクトが
"type"、"kind" などで自分の種類を明示する必要がある
- XMLでは タグ名そのものがオブジェクトの種類 を示すため、別途宣言が不要
- 同じ
/tentativeTaxNetNonRefundableCredits ファクトのJSON表現は、XMLよりむしろ長く複雑
- XMLは コメント(comment) をサポートし、妥当な空白や改行処理など、JSONで当たり前になっている不便さがない
- 属性(attribute)と名前付き子要素(named children)は、言語設計において 何を強調するかを選べる表現力 を提供
- 「ドル」と「整数」の区別のように、固有のデータ型定義 が可能
- 長い説明文を扱う際、XMLはJSONより 読むのも手動編集もはるかに快適
XMLの汎用性とツールエコシステム
- S式、Prolog、KDLなどの代替構文はXMLより読みやすいかもしれないが、XMLを使えば パーサと汎用ツールエコシステムを無料で 手に入れられる
- S式はLispで、Prolog項はPrologでうまく動くが、XMLは どのような形式にも変換可能
- PrologでXMLをProlog項に変換するのは 単一の述語(predicate)1つ で可能
- Hacker News ユーザー ok123456 の「Prolog/Datalog を使えばよいのではないか」という質問にも触れられており、可能ではあるが、汎用性ではXMLが優位
- Chris Given は YAML について「米国税法ロジックをYAMLで表現しようなどと絶対に考えるな」と述べている
- XPath を活用した実例として、シェルコマンド1行でファクトのパスをファジー検索し、選んだパスの定義を即座に確認するスクリプトを作成
cat facts.xml | xpath -q -e '//Fact/@path' | grep -o '/[^" ]*' | fzf でファクトを検索
- 依存関係チェーンをさかのぼり、どのファクトがそのファクトに依存しているかを追跡する機能も追加
- 約60行のbashスクリプトで、ほぼ毎日使うデバッグツールへと発展
- チームメンバーたちも同様の 高速デバッグツール をそれぞれ作り、いずれもXMLを簡単にパースするだけで、Fact Graph の Scala 実装に手を入れず各自の言語で作業
- 中核となる教訓は、汎用データ表現は非常に価値が高く、このカテゴリに属するのはJSONとXMLの2つだけだということ
- ほとんどの場合はJSONを選ぶべきだが、DSLが必要なら XMLが最も安価な選択肢 であり、その費用対効果のおかげでチームはイノベーション予算を別の場所に使える
付記
- プログラマーでない人でも、スキーマ設計が良ければXMLを読むことは可能だが、別のビューを別途構築する のが望ましい
- 最近はXMLへの関心が高まる傾向にあり、Jake Low の XML 文書をフラットな行指向表現へ変換するツール
grex、Martijn Faassen による Rustで実装された最新のXPath/XSLTエンジン Xee などがある
- TWE のファクトは源泉徴収見積もり用であり、税務申告に直接使ってはならない
1件のコメント
Hacker Newsのコメント
XMLは、複数の言語でまともにパースしようとするとコストの高いフォーマットである
実際、標準に近い実装をしようとすると、libxml2、expat、Xercesのような3つのオープンソース実装に依存することになる
SGML系言語の核心は、「リスト」を第一級オブジェクトとして、「ネスト」を第二級オブジェクトとして扱う点であり、タグ名と属性という2つの軸でメタデータを追加できることにある
XMLはDSLとして今でも有用だが、本物のXMLを使うなら「cheap」という言葉は捨てるべきだ
また、宣言的DSLを命令的な数式のように見せることもできる。たとえば
totalOwed = totalTax - totalPaymentsのような式は、XML DSLと同じ意味を持ちうるMETAFONTのような言語がこのアプローチを示している(例へのリンク)
XMLが何度も同じ過ちを繰り返すのをよく見る
フォーマットに機能を多く入れるほど、パースは難しくなるという単純な真理が忘れられがちだ
JSONが人気なのは、機能が少なくパースしやすいからだ
一方でXMLは、attributes、namespaces、CDATA、DTDsなど、あまりにも多くのものを詰め込んでしまった
SQLiteを交換フォーマットとして使おうという議論もあったが、それもXMLのように複雑化する危険がある
CSVが単純だからこそ今なお愛されている理由もここにある
最近JSONにコメントや型情報を無理やり入れようとする試みは、悪いXML属性の再現だ
筆者として同意する
宣言的な仕様を数式のように見せることは可能だが、それは結局新しい言語を作ることだ
そうなると、パーサーをあらゆる環境に移植しなければならないという問題が生じる
演算子の優先順位やswitch式のような文法上の決定も自分で行う必要があり、結局複雑さが爆発する
だからこそ「cheap」という言葉を使った — すでにすべての環境にパーサーとツール群が存在するフォーマットを使うことがコスト削減になる
表現力は落ちるが、小規模チームにとっては賢明な選択だ
エンタープライズJavaでXMLを多用してきたが、メモリとCPUのボトルネックの主犯だった
XMLは決してcheapではない
SGMLの核心は、要素の正規表現ベースのコンテンツモデルにある
単なるリスト構造ではなく、BNFのように文法生成規則を定義できる
「XML proper」ではなく「XML lookalike」と言ったのは、あまりに細かすぎる言い方に思える
XMLの全機能を使っていなくても、XMLは依然としてXMLだ
それは、カップホルダーがないからといってスクールバスを「バスもどき」と呼ぶようなものだ
XMLの代わりに、eDSLをうまく支援する言語を使えばよいと思う
Haskell、OCaml、Scalaのような言語は、applicativeやarrowのような概念で並列計算を簡単に表現できる
JavaScriptでも
.reduce()の代わりにsumのような抽象化を作ればよいXML DSLを作ると、結局は並列化、可読性、新しい文法の発明といった問題を再び解くことになる
複雑なドメインでは、Greenspunの第10法則にぶつかる可能性が高い
ただしHaskellのような言語には、学ぶのが難しいという問題がある
30年の経験がある開発者でも参入障壁が高いと感じる
Rakuもよい選択肢だ
Haskellをベースに始まり、組み込みGrammarと関数型スタイルをサポートしているのでDSLの作成に向いている
HTML!(冗談まじりの短い反応)
Lispでも可能だ
S式を見ると、XMLがどれほど冗長で重たいかがすぐにわかる
JSONの構造はもう少しうまく設計できる
各ノードを1つの型キーと配列値で構成すれば、S式のように表現できる
こうすればストリーミングパースが可能になり、型も先にわかる
大規模データセットで有用だ
JSONはXMLよりはるかに単純で、パースコストが低い
XMLはタグの対応付けや属性処理など状態管理が複雑だが、
JSONは
{}、[]を合わせればよいこの単純さが積み重なってレイテンシ低減につながる
ただしJSONは引用符が多すぎて、視覚的ノイズのように感じられる
個人的にはClojureのEDNのほうがすっきりしていると思う
このようなJSON構造は、美学的には退化した形だと感じる
タグが必要なデータなら、それに合った表現方式を使うほうがよいと思う
The Lost Art of XML の記事のほうが興味深かった
Web開発ツールのかなりの部分が、XMLがブラウザ戦争で敗れた結果として生まれたという見方が印象的だった
ただし「XMLが捨てられたのはJavaScriptが勝ったからだ」という主張には同意しがたい
ブラウザはもともとXMLもサポートしていた(AJAXのXはXMLのことだ)
ただ開発者がXMLを嫌っただけだ
XMLは過剰設計と複雑さのために敬遠されたのだと思う
昔のXML API時代を実際に経験した立場からすると、XMLは本当に苦痛だった
言語ごとにエンコーダー/デコーダーを別々に作る必要があり、保守も大変だった
JSONは単純に配列とオブジェクトにマッピングされるので、言語間互換性に優れている
XMLスキーマ設計会議に時間を浪費していた時代を思うと、JSONはPrettierがタブ vs スペース論争を終わらせたようにAPI設計を単純化した
結局、「複雑なものを学びたくない」という態度から始まったとしても、時間がたつとまた機能が必要になるというパターンだ
ポーランドの税務当局はXMLを愛している
だが彼らのXMLは、人間が読めないほど難解だ
フィールド名が
P_19Nのようになっていて、実際の意味を知るにはスキーマを見なければならない法令の条項番号まで入っている
皮肉なことに、VAT法案の作成者はいま税務コンサルティングをしている
私はS式ベースのDSLを実際に使っている
WebAssemblyベースのデスクトップブラウザーランタイムでHTMLとCSSの役割を果たしており、
ドキュメント同期の問題を解決する独自のマークアップ言語にも再利用している
関連例はCanvasUIサンプルコードとスタイルファイル、ドキュメント化ツールで見られる
候補者が自分で簡単な言語を実装してみせた瞬間の反応が印象的だった
XMLはDSLというより汎用のパーサー/レクサーツールだ
テキストをASTに変換するだけで、実際のDSLはその上に定義される仕様である
機能が多く複雑だが、ツール生態系が豊富という利点がある
手で書くより、生成されたテキストの処理に向いている
XSDスキーマ検証が組み込まれているので、文書の整合性を即座に確認できる
自動化ツールを使わずにXMLが難しいと不満を言うのは、逆アセンブラなしでバイナリを扱うようなものだ
ただしスキーマ検証だけで内容の正しさを保証できるわけではない
型チェックがプログラムの正しさを保証しないのと同じ理屈だ
XSDは有用だが複雑で制約も多い
そのため一部のXMLコミュニティはRELAX-NGへ移ったが、完全には置き換えられなかった
どんな作業でXSD検証が本当に必要なのか気になる
XMLはマークアップ言語としては問題なく、データ交換フォーマットとしても使えるが、プログラミング言語として使うとひどい
JSONも同様で、データ交換には向いているが、言語として使うと後悔する
AnsibleのようなYAMLベース言語がその例だ
一方、LispのS式はJSONに似た構造を持ちながらも、優れた言語へと発展した
XMLの問題はXML自体というより、よいXMLを生成しにくいことにある
標準が複雑で、生成側ごとに表現方法が異なるため一貫性に欠ける
JSONはこのばらつきがはるかに小さい
金融機関のXMLを見ると絶望的な気分になるほどだ
XMLの問題は結局、ツールの遅さと不完全なバリデーターのせいだ
データ表現の複雑さより、ツール品質のほうが大きなボトルネックだった
実際の問題は「よいXML」より、ひどいXMLをあまりにも簡単に作れてしまったことだ
そのためコミュニティは、名前空間、検証、変換、セマンティックWebなどによって相互運用性を確保しようと努めた
完全な合意が不可能な環境でも仕事を進めるための妥協だった