XML은 값싼 DSL이다
(unplannedobsolescence.com)- 미국 국세청이 새로운 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는 두 개의 XML 설정으로 생성되는 정적 사이트이며, 첫 번째는 미국 세법을 표현한 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에서는 태그 이름 자체가 객체의 종류를 나타내므로 별도 선언 불필요
- JSON의 유일한 복합 데이터 구조는 객체이므로, 모든 자식 객체가
- 동일한
/tentativeTaxNetNonRefundableCredits팩트의 JSON 표현이 XML보다 오히려 길고 복잡 - XML은 주석(comment) 지원, 합리적인 공백/줄바꿈 처리 등 JSON에서 당연시되는 불편함이 없음
- 속성(attribute)과 명명된 자식 요소(named children)가 언어 설계에서 무엇을 강조할지 선택할 수 있는 표현력을 제공
- "달러"와 "정수"의 구분처럼 고유한 데이터 타입 정의가 가능
- 긴 설명 텍스트를 다룰 때 XML이 JSON보다 읽기와 수동 편집에 훨씬 쾌적
XML의 범용성과 도구 생태계
- S-expression, Prolog, KDL 등 대안 문법이 XML보다 읽기 좋을 수 있지만, XML을 사용하면 파서와 범용 도구 생태계를 무료로 획득
- S-expression은 Lisp에서, Prolog 항은 Prolog에서 잘 동작하지만, XML은 어떤 형식으로든 변환 가능
- Prolog에서 XML을 Prolog 항으로 변환하는 것은 단일 술어(predicate) 하나로 가능
- Hacker News 사용자 ok123456의 "Prolog/Datalog를 쓰면 되지 않느냐"는 질문도 언급되었으며, 가능하지만 범용성에서 XML이 우위
- Chris Given은 YAML에 대해 "절대로 미국 세법 로직을 YAML로 표현하려 하지 말 것"이라고 언급
-
XPath를 활용한 실제 사례: 셸 명령 한 줄로 팩트 경로를 퍼지 검색하고, 선택한 경로의 정의를 즉시 조회하는 스크립트를 작성
-
cat facts.xml | xpath -q -e '//Fact/@path' | grep -o '/[^"]*' | fzf로 팩트 검색 - 의존성 체인을 거슬러 올라가 어떤 팩트가 해당 팩트에 의존하는지 추적하는 기능도 추가
- 약 60줄의 bash 스크립트로 거의 매일 사용하는 디버깅 도구로 발전
-
- 팀원들도 유사한 빠른 디버깅 도구를 각자 만들었으며, 모두 XML을 사소하게 파싱하여 Fact Graph의 Scala 구현을 건드리지 않고 각자의 언어로 작업
- 핵심 교훈: 범용 데이터 표현은 매우 가치가 높으며, 이 범주에는 JSON과 XML 두 가지만 존재
- 대부분의 경우 JSON을 선택해야 하지만, DSL이 필요하면 XML이 가장 저렴한 선택지이며, 그 비용 효율성 덕분에 팀이 혁신 예산을 다른 곳에 사용 가능
부가 사항
- 프로그래머가 아닌 사람도 스키마 설계가 잘 되어 있으면 XML을 읽을 수 있으며, 다만 대안 뷰를 별도 구축하는 것이 바람직
- 최근 XML에 대한 관심이 증가하는 추세: Jake Low의 XML 문서를 평면 라인 지향 표현으로 변환하는 도구
grex, Martijn Faassen의 Rust로 구현한 최신 XPath/XSLT 엔진 Xee 등 - TWE의 팩트는 원천징수 추정용이므로 세금 신고에 직접 사용하면 안 됨
Hacker News 의견들
-
XML은 여러 언어에서 제대로 파싱하려면 비용이 많이 드는 포맷임
실제로 표준에 가깝게 구현하려면 libxml2, expat, Xerces 같은 세 가지 오픈소스 구현체에 의존해야 함
SGML 계열 언어의 핵심은 “리스트”를 1급 객체로, 중첩을 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-expression을 보면 XML이 얼마나 장황하고 무겁게 느껴지는지 바로 알 수 있음
-
-
JSON 구조를 좀 더 잘 설계할 수 있음
각 노드를 타입 키 하나와 배열 값으로 구성하면 S-expression처럼 표현 가능함
이렇게 하면 스트리밍 파싱이 가능하고, 타입을 먼저 알 수 있음
대규모 데이터셋에서 유용함-
JSON은 XML보다 훨씬 단순하고 파싱 비용이 낮음
XML은 태그 짝 맞추기, 속성 처리 등 상태 관리가 복잡하지만
JSON은{},[]만 맞추면 됨
이런 단순함이 누적되어 지연 시간 감소로 이어짐 -
하지만 JSON의 따옴표가 너무 많아서 시각적 노이즈처럼 느껴짐
개인적으로는 Clojure의 EDN이 더 깔끔하다고 생각함 -
이런 JSON 구조는 미학적으로 퇴화된 형태라고 느낌
태그가 필요한 데이터라면 그에 맞는 표현 방식을 쓰는 게 낫다고 생각함
-
-
The Lost Art of XML 글이 더 흥미로웠음
웹 개발 도구의 상당수가 XML이 브라우저 전쟁에서 패배한 결과로 생겨났다는 관점이 인상적이었음-
하지만 “XML이 버려진 건 JavaScript가 이겼기 때문”이라는 주장은 동의하기 어려움
브라우저는 원래 XML도 지원했음 (AJAX의 X가 XML임)
단지 개발자들이 XML을 싫어했을 뿐임
XML은 과도한 설계와 복잡성 때문에 외면받았다고 생각함 -
예전 XML API 시대를 직접 겪어본 입장에서, XML은 정말 고통스러웠음
각 언어마다 인코더/디코더를 따로 만들어야 했고, 유지보수도 힘들었음
JSON은 단순히 배열과 객체로 매핑되어서 언어 간 호환성이 뛰어남
XML 스키마 설계 회의에 시간을 낭비하던 시절을 생각하면, JSON은 Prettier가 탭 vs 스페이스 논쟁을 끝낸 것처럼 API 설계를 단순화했음 -
결국 “복잡한 걸 배우기 싫다”는 태도에서 출발했지만, 시간이 지나면 다시 기능이 필요해지는 패턴임
-
-
폴란드 세무당국은 XML을 사랑함
하지만 그들의 XML은 사람이 읽을 수 없을 정도로 난해함
필드명이P_19N같은 식으로 되어 있고, 실제 의미를 알기 위해 스키마를 봐야 함
심지어 법 조항 번호까지 들어감
아이러니하게도 VAT 법안 작성자가 현재 세무 컨설팅을 하고 있음 -
나는 S-expression 기반 DSL을 직접 사용 중임
WebAssembly 기반 데스크톱 브라우저 런타임에서 HTML과 CSS 역할을 하며,
문서 동기화 문제를 해결하는 자체 마크업 언어에도 재활용함
관련 예시는 CanvasUI 예제 코드와 스타일 파일, 문서화 도구에서 볼 수 있음- S-expression은 파서 구현이 매우 단순해서 인터뷰 문제로도 썼었음
지원자가 직접 간단한 언어를 구현해내는 순간의 반응이 인상적이었음
- S-expression은 파서 구현이 매우 단순해서 인터뷰 문제로도 썼었음
-
XML은 DSL이라기보다 일반 파서/렉서 도구임
텍스트를 AST로 변환할 뿐, 실제 DSL은 그 위에 정의하는 명세임
기능이 많고 복잡하지만, 툴링 생태계가 풍부하다는 장점이 있음
수동 작성보다는 생성된 텍스트 처리에 적합함 -
XSD 스키마 검증이 내장되어 있어서 문서의 정합성을 즉시 확인할 수 있음
자동화 도구를 쓰지 않고 XML이 어렵다고 불평하는 건, 디스어셈블러 없이 바이너리를 다루는 것과 같음-
하지만 스키마 검증만으로 내용의 올바름을 보장할 수는 없음
타입 체크가 프로그램의 정확성을 보장하지 않는 것과 같은 이치임 -
XSD는 유용하지만 복잡하고 제약이 많음
그래서 일부 XML 커뮤니티는 RELAX-NG로 옮겨갔지만 완전히 대체되진 못했음 -
어떤 작업이 XSD 검증을 꼭 필요로 하는지 궁금함
-
-
XML은 마크업 언어로는 괜찮고, 데이터 교환 포맷으로도 쓸 만하지만, 프로그래밍 언어로 쓰면 끔찍함
JSON도 마찬가지로, 데이터 교환에는 좋지만 언어로 쓰면 후회함
Ansible처럼 YAML 기반 언어들이 그 예임
반면 Lisp의 S-expression은 JSON과 비슷한 구조이면서도 훌륭한 언어로 발전했음 -
XML의 문제는 XML 자체보다 좋은 XML을 생성하기 어려운 점임
표준이 복잡하고, 생산자마다 표현 방식이 달라서 일관성이 떨어짐
JSON은 이 표준편차가 훨씬 작음
금융기관의 XML을 보면 절망감이 들 정도임-
XML의 문제는 결국 툴링의 느림과 불완전한 검증기 때문임
데이터 표현의 복잡성보다 도구 품질이 더 큰 병목이었음 -
사실 문제는 “좋은 XML”보다 “엉망인 XML”을 너무 쉽게 만들 수 있었다는 점임
그래서 커뮤니티는 네임스페이스, 검증, 변환, 시맨틱 웹 등으로 상호운용성을 확보하려고 노력했음
완벽한 합의가 불가능한 환경에서도 일을 진행하기 위한 타협이었음
-