- El Servicio de Impuestos Internos de EE. UU. publicó como código abierto el nuevo Tax Withholding Estimator (TWE), y el principio clave de diseño es una estructura que modela la legislación fiscal estadounidense como una especificación declarativa basada en XML
- La lógica de cálculo de impuestos de TWE está construida sobre un motor lógico llamado Fact Graph, que expresa cada concepto fiscal como un grafo de dependencias de "hechos" (Facts) definidos en XML
- Si la lógica fiscal se implementa en un lenguaje imperativo como JavaScript, surgen problemas como la gestión del orden de ejecución, la pérdida de valores intermedios y la exposición de detalles de implementación, por lo que el enfoque declarativo resulta esencial
- JSON no es adecuado para manejar expresiones anidadas arbitrarias, mientras que en XML las etiquetas mismas indican el tipo de objeto, así que es mucho más conveniente para construir un DSL
- XML permite aprovechar gratuitamente un ecosistema maduro de herramientas como XPath, por lo que es la opción más rentable para especificaciones declarativas multiplataforma
Fact Graph: la legislación fiscal de EE. UU. expresada en XML
- El Tax Withholding Estimator (TWE) publicado por el IRS es una herramienta que permite a los contribuyentes ingresar ingresos y deducciones para estimar impuestos y montos de retención
- El proyecto está publicado como código abierto y también acepta contribuciones del público
- TWE es un sitio estático generado a partir de dos configuraciones XML; la primera es el Fact Dictionary que representa la legislación fiscal estadounidense
- Fact Graph es un motor lógico creado originalmente para el proyecto IRS Direct File, y calcula la obligación tributaria y las retenciones de un contribuyente con base en los hechos definidos en el Fact Dictionary
- Cada hecho se define en XML; por ejemplo,
/totalOwed se expresa como un hecho derivado (Derived) que resta /totalPayments de /totalTax
- El "total adeudado (total owed)" es la diferencia entre el impuesto total sobre los ingresos (total tax) y el monto ya pagado (total payments)
- Los créditos reembolsables (refundable credits) son créditos fiscales que pueden volver negativo el saldo de impuestos, y suman elementos como Earned Income Credit, Child Tax Credit y American Opportunity Credit con
<Add>
- Los créditos no reembolsables (non-refundable credits) solo pueden reducir la carga fiscal hasta 0, y usan el operador
<GreaterOf> para elegir el mayor valor entre 0 y (impuesto tentativo - créditos no reembolsables)
- Los valores ingresados por el usuario usan la etiqueta
<Writable> en lugar de <Derived>, y especifican el tipo de valor con <Dollar/>, <Boolean/> y similares
- Los hechos dependen entre sí en una estructura de grafo que produce la cifra fiscal final
Por qué la lógica fiscal necesita una especificación declarativa
- Si el mismo cálculo se escribiera en JavaScript, sería breve como
const totalOwed = totalTax - totalPayments, pero ese enfoque es imperativo, se ejecuta de forma secuencial y hace que se pierdan los pasos intermedios
- Cuando las dependencias se profundizan, aparece el problema del orden de ejecución: una función de entrada del usuario como
getInput() bloquea todos los cálculos posteriores, y las propias preguntas deben cambiar según si existe cónyuge, por ejemplo
- En la lógica para sumar ingresos de Social Security quedan expuestos detalles de implementación de JavaScript como
map/reduce, mientras que <CollectionSum> en XML expresa el concepto matemático fiscal en sí
- Con
<Dependency path="/socialSecuritySources/*/totalFederalTaxesPaid"/> se suman los elementos dentro de una colección
- El Fact Dictionary sigue un enfoque declarativo, donde no se describen los pasos concretos de ejecución ni su orden; basta con describir cálculos con nombre y relaciones de dependencia, y el motor decide automáticamente cómo ejecutarlos
- La ventaja más importante del modelo fiscal declarativo es la auditabilidad y la introspección interna: se le puede preguntar al programa "¿cómo llegaste a este número?"
- En un programa imperativo, los valores intermedios ya se descartaron y solo pueden verse con registros o un depurador; esto no escala cuando hay cientos de cálculos intermedios, como en la legislación fiscal de EE. UU.
- Según Chris Given, autor original de Fact Graph, Fact Graph es "un medio para demostrar que los elementos no preguntados no cambiaron el resultado de la declaración y que se están recibiendo todos los beneficios fiscales aplicables"
- Intuit, creadora de TurboTax, llegó a la misma conclusión y publicó en 2020 un libro blanco sobre un "Tax Knowledge Graph", aunque su implementación no es pública
- El Fact Graph del IRS es código abierto y de dominio público, por lo que cualquiera puede estudiarlo, compartirlo y ampliarlo
Por qué XML es mucho más adecuado que JSON para un DSL
- Si se intenta usar JSON como formato de representación declarativa para la legislación fiscal, el manejo de expresiones arbitrariamente anidadas se vuelve muy incómodo
- Como la única estructura de datos compuesta de JSON es el objeto, todos los objetos hijos tienen que indicar su tipo con algo como
"type", "kind", etc.
- En XML, el propio nombre de la etiqueta indica el tipo del objeto, así que no hace falta una declaración adicional
- La representación en JSON del mismo hecho
/tentativeTaxNetNonRefundableCredits termina siendo más larga y compleja que la de XML
- XML admite comentarios (comments) y un manejo razonable de espacios y saltos de línea, evitando incomodidades que en JSON se dan por sentadas
- Los atributos (attributes) y los elementos hijos con nombre (named children) ofrecen una capacidad expresiva que permite elegir qué enfatizar en el diseño del lenguaje
- Es posible definir tipos de datos propios, como la distinción entre "dólar" y "entero"
- Al tratar textos explicativos largos, XML es mucho más cómodo de leer y editar manualmente que JSON
La universalidad de XML y su ecosistema de herramientas
- Sintaxis alternativas como S-expression, Prolog o KDL pueden resultar más legibles que XML, pero usar XML permite obtener gratis el parser y un ecosistema de herramientas de propósito general
- Las S-expressions funcionan bien en Lisp y los términos de Prolog en Prolog, pero XML puede transformarse a cualquier formato
- Convertir XML a términos de Prolog puede hacerse con un solo predicado (predicate)
- También se menciona la pregunta del usuario de Hacker News ok123456 de si no bastaría usar "Prolog/Datalog"; sí es posible, pero XML tiene ventaja en universalidad
- Sobre YAML, Chris Given comentó: "jamás intentes expresar la lógica de la legislación fiscal estadounidense en YAML"
- XPath se usa en un caso práctico: se escribió un script que, con una sola línea de shell, busca de forma difusa rutas de hechos y consulta de inmediato la definición de la ruta seleccionada
cat facts.xml | xpath -q -e '//Fact/@path' | grep -o '/[^"]*' | fzf para buscar hechos
- También se añadió una función para retroceder por la cadena de dependencias y rastrear qué hechos dependen de ese hecho
- Con unas 60 líneas de bash, esto evolucionó a una herramienta de depuración que se usa casi a diario
- Los integrantes del equipo también construyeron cada uno herramientas rápidas de depuración similares, todas parseando XML de forma trivial y trabajando en sus propios lenguajes sin tocar la implementación en Scala de Fact Graph
- La lección central es que una representación de datos universal es muy valiosa, y en esta categoría solo existen JSON y XML
- En la mayoría de los casos conviene elegir JSON, pero cuando se necesita un DSL, XML es la opción más barata, y esa eficiencia de costos permite al equipo usar su presupuesto de innovación en otras áreas
Información adicional
- Incluso personas que no son programadoras pueden leer XML si el esquema está bien diseñado, aunque es recomendable construir vistas alternativas por separado
- Últimamente está creciendo el interés por XML:
grex, la herramienta de Jake Low para convertir documentos XML a una representación plana orientada a líneas, y Xee, el moderno motor XPath/XSLT implementado en Rust de Martijn Faassen, son algunos ejemplos
- Los hechos de TWE son para estimar retenciones, por lo que no deben usarse directamente para presentar una declaración de impuestos
1 comentarios
Comentarios en Hacker News
XML es un formato costoso si quieres parsearlo correctamente en varios lenguajes
En la práctica, para implementarlo de forma cercana al estándar hay que depender de tres implementaciones open source como libxml2, expat y Xerces
La esencia de los lenguajes de la familia SGML es que tratan a las “listas” como objetos de primera clase y al anidamiento como objetos de segunda clase, y permiten agregar metadatos en dos ejes: nombres de etiquetas y atributos
XML sigue siendo útil como DSL, pero si vas a usar XML de verdad, hay que dejar de lado la palabra “cheap”
Además, un DSL declarativo también puede hacerse parecer una fórmula imperativa. Por ejemplo, una expresión como
totalOwed = totalTax - totalPaymentspuede tener el mismo significado que un DSL en XMLLenguajes como METAFONT muestran este enfoque (enlace de ejemplo)
A menudo veo que XML repite el mismo error una y otra vez
Mucha gente olvida la verdad simple de que cuantas más funciones le metes a un formato, más difícil es parsearlo
JSON es popular porque tiene pocas funciones y por eso es fácil de parsear
En cambio, XML metió demasiadas cosas: attributes, namespaces, CDATA, DTDs, etc.
También hubo discusiones sobre usar SQLite como formato de intercambio, pero eso también corre el riesgo de volverse tan complejo como XML
Esa es también la razón por la que CSV sigue siendo querido: por su simplicidad
Hoy en día, los intentos de forzar comentarios o información de tipos dentro de JSON son una repetición de los malos atributos de XML
Como autor del texto, estoy de acuerdo
Es posible hacer que una especificación declarativa se vea como una fórmula matemática, pero al final eso implica crear un lenguaje nuevo
Y entonces aparece el problema de portar el parser a todos los entornos
También hay que decidir por cuenta propia cosas sintácticas como la precedencia de operadores o expresiones
switch, y al final la complejidad se disparaPor eso usé la palabra “cheap”: reduce costos usar un formato para el que ya existen parsers y tooling en todos los entornos
La expresividad baja, pero para equipos pequeños es una decisión sensata
He usado mucho XML en Java empresarial, y era uno de los principales culpables de cuellos de botella de memoria y CPU
XML no es cheap en absoluto
El núcleo de SGML es su modelo de contenido basado en expresiones regulares
No se trata solo de estructuras de listas; también permite definir reglas de producción gramaticales como en BNF
Decir que no es “XML proper” sino un “XML lookalike” me parece una forma demasiado quisquillosa de expresarlo
XML sigue siendo XML aunque no uses todas sus funciones
Es como llamar “imitación de autobús” a un autobús escolar porque no tiene portavasos
Creo que en vez de XML basta con usar un lenguaje con buen soporte para eDSL
Lenguajes como Haskell, OCaml y Scala permiten expresar cálculos paralelos fácilmente con conceptos como applicative o arrow
En JavaScript también se puede crear una abstracción como
sumen lugar de usar.reduce()Si haces un DSL en XML, al final tienes que volver a resolver problemas como paralelización, legibilidad y la invención de una sintaxis nueva
En dominios complejos, es muy probable toparse con la décima ley de Greenspun
Pero lenguajes como Haskell tienen el problema de que son difíciles de aprender
Incluso un desarrollador con 30 años de experiencia puede sentir que la barrera de entrada es alta
Raku también es una buena opción
Comenzó con una base inspirada en Haskell y ofrece Grammar integrado y estilo funcional, lo que lo hace favorable para escribir DSL
¡HTML! (respuesta corta en tono de broma)
Lisp también sirve
Cuando ves S-expression, te das cuenta de inmediato de lo verboso y pesado que se siente XML
La estructura de JSON se puede diseñar mejor
Si cada nodo se compone de una sola clave de tipo y un valor de arreglo, puede expresarse como un S-expression
Así se vuelve posible el parsing en streaming y se puede conocer el tipo de antemano
Eso resulta útil en conjuntos de datos grandes
JSON es mucho más simple que XML y tiene menor costo de parsing
XML requiere manejo de estado complejo para emparejar etiquetas, procesar atributos, etc.
En JSON basta con emparejar
{}y[]Esa simplicidad acumulada termina traduciéndose en menor latencia
Pero JSON tiene demasiadas comillas y se siente como ruido visual
Personalmente, creo que EDN de Clojure se ve más limpio
Siento que ese tipo de estructura JSON es una forma degenerada desde lo estético
Si los datos necesitan etiquetas, me parece mejor usar una forma de representación acorde a eso
El texto The Lost Art of XML me pareció más interesante
Me llamó la atención la idea de que gran parte de las herramientas de desarrollo web surgieron como resultado de que XML perdió en las guerras del navegador
Pero me cuesta estar de acuerdo con la afirmación de que “XML fue descartado porque JavaScript ganó”
Los navegadores originalmente también soportaban XML (la X de AJAX es XML)
Lo que pasó fue que a los desarrolladores simplemente no les gustaba XML
Creo que XML fue dejado de lado por su sobreingeniería y complejidad
Como alguien que vivió directamente la era de las API en XML, XML era realmente doloroso
Había que crear encoders/decoders por separado en cada lenguaje, y mantenerlos también era difícil
JSON simplemente se mapea a arreglos y objetos, así que la compatibilidad entre lenguajes es excelente
Si pienso en el tiempo que se desperdiciaba en reuniones para diseñar esquemas XML, JSON simplificó el diseño de APIs igual que Prettier acabó con la discusión de tabs vs spaces
Al final, esto parte de la actitud de “no quiero aprender algo complejo”, pero con el tiempo vuelven a hacer falta más funciones
A la autoridad fiscal polaca le encanta XML
Pero su XML es tan críptico que un humano no puede leerlo
Los nombres de campo son cosas como
P_19N, y hay que ver el esquema para entender su significado realIncluso incluyen números de artículos de ley
Irónicamente, quien redactó la ley del IVA ahora trabaja en consultoría fiscal
Yo uso directamente un DSL basado en S-expression
Cumple el papel de HTML y CSS en un runtime de navegador de escritorio basado en WebAssembly,
y también lo reutilizo en un lenguaje de marcado propio que resuelve problemas de sincronización de documentos
Se pueden ver ejemplos relacionados en el código de ejemplo de CanvasUI, el archivo de estilos y la herramienta de documentación
La reacción de los candidatos cuando lograban implementar por sí mismos un lenguaje sencillo era memorable
XML, más que un DSL, es una herramienta general de parser/lexer
Solo transforma texto en un AST; el DSL real es la especificación que defines encima
Tiene muchas funciones y es complejo, pero tiene la ventaja de un ecosistema de tooling rico
Es más adecuado para procesar texto generado que para escribirlo a mano
Como trae validación de esquemas XSD incorporada, se puede verificar de inmediato la consistencia del documento
Quejarse de que XML es difícil sin usar herramientas de automatización es como manejar binarios sin desensamblador
Pero la validación de esquemas por sí sola no puede garantizar la corrección del contenido
Es el mismo principio de que el chequeo de tipos no garantiza la corrección de un programa
XSD es útil, pero complejo y lleno de restricciones
Por eso parte de la comunidad XML se pasó a RELAX-NG, aunque no logró reemplazarlo por completo
Me da curiosidad qué tipo de trabajo requiere necesariamente validación con XSD
XML está bien como lenguaje de marcado y es aceptable como formato de intercambio de datos, pero es terrible como lenguaje de programación
Con JSON pasa algo parecido: es bueno para intercambio de datos, pero usarlo como lenguaje termina en arrepentimiento
Los lenguajes basados en YAML, como Ansible, son un ejemplo de eso
En cambio, los S-expression de Lisp tienen una estructura parecida a JSON y aun así evolucionaron hacia un gran lenguaje
El problema de XML no es tanto XML en sí, sino que es difícil generar buen XML
El estándar es complejo y cada productor expresa las cosas de manera distinta, así que la consistencia se pierde
En JSON esa desviación estándar es mucho menor
Ver XML de instituciones financieras puede llegar a ser desmoralizante
El problema de XML, al final, era más bien la lentitud del tooling y los validadores incompletos
Más que la complejidad de la representación de datos, el verdadero cuello de botella era la calidad de las herramientas
En realidad, el problema no era tanto “buen XML”, sino que era demasiado fácil crear XML desastroso
Por eso la comunidad intentó asegurar interoperabilidad con namespaces, validación, transformación, web semántica, etc.
Era una solución de compromiso para poder avanzar incluso en entornos donde un consenso perfecto era imposible