Ver CSS como un lenguaje de consultas
(evdc.me)- Los selectores y declaraciones de CSS ya tienen una similitud estructural con Datalog en el sentido de que eligen un conjunto de elementos ya existentes y aplican propiedades al resultado, igual que Datalog consulta relaciones y produce hechos.
- El CSS normal no soporta cálculo recursivo donde el resultado de una selección se reutiliza como condición de selección, por lo que no puede expresar directamente una propagación de estado como un tema dark que se transmite a descendientes y se detiene en un límite light.
- En el CSSLog hipotético, se permite agregar clases que afectan el matching de selectores, de modo que un estado derivado como
.effectively-darkpueda propagarse recursivamente y el cálculo se repita hasta que ya no aparezcan resultados nuevos. - Este tipo de cálculo se explica con el fixpoint y la monotonicity de Datalog: para que la evaluación iterativa termine de forma finita, solo deben agregarse hechos y no eliminarse.
- Incluso las container queries del CSS real pueden leer el estado de ancestros, pero no consultar estados recursivos derivados, así que CSS puede acercarse a Datalog hasta cierto punto sin cruzar los límites del motor de renderizado del navegador.
Correspondencia básica entre CSS y Datalog
- CSS parte de la base de que existen objetos previamente definidos, que aquí son los elementos HTML.
- Elementos como
h1,aodivya existen fuera de CSS, y CSS no los declara de nuevo. - Como ejemplo se mencionan elementos con atributos como
class,idydata-custom-attribute.
- Elementos como
- Los selectores CSS apuntan a conjuntos con condiciones en común, y pueden restringir el objetivo por nombre de etiqueta,
id,classo valor de atributo.- Aparecen como ejemplos selectores como
div,#child,.awesomey[data-custom-attribute="foo"]. - También se puede expresar el objetivo según su posición en la jerarquía del documento, y combinar selectores para formar intersecciones.
- Aparecen como ejemplos selectores como
- Un selector combinado como
div.awesomerealiza una intersección de conjuntos, seleccionando solo elementos que seandivy a la vez.awesome.- Esta idea de intersección conecta después con el concepto de join en Datalog.
- Una regla CSS une selector y declaración para aplicar valores de propiedades al conjunto seleccionado.
- En el ejemplo
div.awesome { color: red; font-size: 24px }, se definencoloryfont-sizepara esos elementos. - En el navegador, el resultado sería texto rojo y grande.
- En el ejemplo
Límites de CSS y el problema de las consultas recursivas
- El CSS normal es bueno para cambiar propiedades fuera del lenguaje, pero no puede reutilizar directamente ese resultado como condición de selección.
- Puede asignar el color de un elemento, pero el navegador rechaza algo como
div[color=red], donde el color mismo sería parte del selector. - Una regla que volviera a aplicar
color: bluea elementos concolor: redtendría una semántica ambigua.
- Puede asignar el color de un elemento, pero el navegador rechaza algo como
- El ejemplo del modo oscuro en un sistema de diseño se presenta como un problema que requiere propagación transitiva de estado.
- Se quiere aplicar un outline de enfoque blanco a todos los elementos interactivos dentro de una tarjeta con
data-theme="dark". - Pero si en medio aparece un
data-theme="light", la propagación debe detenerse debajo de ese punto.
- Se quiere aplicar un outline de enfoque blanco a todos los elementos interactivos dentro de una tarjeta con
- En el CSS real solo se puede cubrir parcialmente añadiendo excepciones.
- Se puede crear una regla base con
[data-theme="dark"] :focus { outline-color: white; }. - Y revertir en el límite light con
[data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }. - Pero este enfoque obliga a seguir agregando reglas conforme aumenta la profundidad del anidamiento.
- Se puede crear una regla base con
- El problema requiere una definición relacional recursiva, y CSS no puede expresarla.
- Hace falta una definición como: “es effectively-dark si es dark por sí mismo, o si tiene un ancestro effectively-dark y no hay un ancestro effectively-light entre ambos”.
- El texto original lo llama una recursive relational definition y afirma que CSS no puede representarla.
Sintaxis hipotética de CSSLog
- CSSLog se plantea como una versión hipotética que conserva selectores y asignación de propiedades como CSS normal, pero además permite cambiar propiedades que afectan el matching de selectores.
- En el ejemplo aparece una sintaxis como
class: +barpara agregar una clase. - También se supone una forma como
+<div class="baz">para crear un nuevo elemento hijo. - Sobre eliminar elementos solo se dice “probablemente no”, sin más detalle.
- En el ejemplo aparece una sintaxis como
- Después de ejecutar una regla
div.foo, el mismo elemento podría pasar a hacer match condiv.bar, así que el resultado de una regla influye en los siguientes matches.- Desde este punto ya no basta una sola aplicación hacia adelante, sino que hace falta evaluación iterativa.
- Al trasladar el ejemplo del modo oscuro a CSSLog, se vuelve posible la propagación recursiva de clases derivadas.
- Se empieza con
[data-theme="dark"] { class: +effectively-dark; }. - Luego se propaga a los hijos con
.effectively-dark > :not([data-theme="light"]) { class: +effectively-dark; }. - Y se aplica el estilo final con
.effectively-dark :focus { outline-color: white; }.
- Se empieza con
- La segunda regla se describe como una propagación recursiva hasta el límite light, y se detiene al llegar al estado deseado.
- El texto remarca que el CSS actual no puede hacer esto, y al final retoma algunos rodeos parecidos.
Estructura de Datalog y parecido con CSS
- En Datalog, los objetos se llaman atoms y pasan a existir en el momento en que se mencionan.
- Nombres como
aliceybobpueden usarse sin declaración previa. - El texto los compara con los
:symbolsde Ruby.
- Nombres como
- Los conjuntos y relaciones se expresan con relations y tuples.
parent(alice, bob)es una tupla dentro de la relaciónparent.parentse explica como el conjunto de pares donde el primer objeto es padre del segundo.
- Las variables se usan para hacer match en consultas y seleccionar conjuntos.
parent(bob, X)significa todos losXde los que Bob es padre.- En el ejemplo,
Xse evalúa comocarolydave. - Por convención, las variables van en mayúscula y los atoms y relations en minúscula.
- Repetir el mismo nombre de variable provoca un join.
mother(X, Y) :- parent(X, Y), woman(X).crea la relación de madres como intersección entre el conjunto de padres y el conjunto de mujeres.- El texto lo describe como la intersección entre “todos los padres” y “todas las mujeres”.
- En una regla Datalog,
:-se lee como if: cuando todas las condiciones del body del lado derecho se cumplen, se agrega como verdadero el hecho del head a la izquierda.- La coma en el body se lee como and.
ancestor(X, Y) :- parent(X, Y).significa que si X es padre de Y, entonces X es ancestro de Y.
- CSS y Datalog se comparan como estructuras parecidas pero invertidas.
color(X, red) :- div(X), class(X, awesome).significa “el color de X es rojo si X es un div y tiene la clase awesome”.- Se presenta como equivalente semántico de
div.awesome { color: red; }en CSS. - El texto resume que el selector corresponde al body y la declaración al head.
Recursión y hechos derivados
- En Datalog, “hacer” algo significa derivar hechos nuevos.
- Funciona agregando nuevas tuplas a relaciones a partir de hechos ya existentes.
- El ejemplo de
ancestorse presenta como un caso típico de reglas recursivas.ancestor(X, Y) :- parent(X, Y).convierte una relación de padre directo en ancestro.ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).la extiende siguiendo ancestros del padre.
- La segunda regla incluye recursión autorreferencial, porque
ancestoraparece tanto en el head como en el body.- Gracias a eso se derivan relaciones indirectas como
alice -> bob -> carolyalice -> bob -> dave.
- Gracias a eso se derivan relaciones indirectas como
- El resultado del ejemplo enumera estos cinco hechos:
ancestor(alice, bob)ancestor(bob, carol)ancestor(bob, dave)ancestor(alice, carol)ancestor(alice, dave)
- También se menciona que SQL no podía hacer este tipo de cálculo antes de
WITH RECURSIVE, y que esa función apareció por la demanda de computación recursiva.- Aun así, el texto aclara que la sintaxis y semántica recursiva de SQL no siempre se combinan bien con otras partes del lenguaje.
- Datalog no necesita un
forexplícito para seguir iterando: el motor continúa hasta producir todos los resultados necesarios.- La siguiente sección conecta esto con el concepto de fixpoint.
Fixpoint y monotonicity
- El cascade del CSS normal se describe como una aplicación hacia adelante de una sola pasada.
- El navegador lee las reglas, calcula el matching de selectores, aplica las declaraciones y termina.
- No hay un loop de retroalimentación.
- En CSSLog y en Datalog real, los resultados de una regla pueden volver a satisfacer condiciones de otra regla.
- Una regla puede cambiar una propiedad, eso hace que otra regla vuelva a ejecutarse, y a su vez termine afectando también a la primera.
- Un motor ingenuo de Datalog repite la aplicación de reglas hasta que ya no aparezcan hechos nuevos.
- Empieza con los hechos base explícitos.
- Hace match entre los body de todas las reglas y el conjunto actual de hechos.
- Si encuentra coincidencias, agrega el hecho del head.
- Si aparecen hechos nuevos, repite; si no, termina.
- A ese punto de terminación se le llama fixpoint.
- Es el estado en el que volver a aplicar todas las reglas ya no produce resultados nuevos.
- El ejemplo de
ancestorse resume en tres rondas.- En la primera, los hechos
parentagregan tres relaciones de ancestro directo. - En la segunda, se agregan dos relaciones indirectas de
alice. - En la tercera, ya no aparece ningún hecho nuevo y se alcanza el fixpoint.
- En la primera, los hechos
- La razón por la que esto puede terminar está en la monotonicity.
- Como no se eliminan hechos y solo se agregan, el conjunto de hechos conocidos solo puede crecer.
- Con hechos iniciales finitos y una cantidad finita de hechos derivables, el proceso se detiene tras un número finito de pasos.
- Si en cambio se permitiera eliminar hechos, resultados posteriores podrían deshacer resultados previos, y esa propiedad se rompe.
- El texto lo describe como Infinite Loop Land, y por eso sugiere que es mejor que CSSLog no permita borrar elementos.
- En sistemas distribuidos, la monotonicity también se relaciona con la posibilidad de lograr consistencia sin coordinación costosa, según una nota al pie.
- Consistency As Logical Monotonicity enlace relacionado 1: sistemas distribuidos y monotonicity
- artículo relacionado 2: material adicional
Por qué importa
- La comparación entre CSS y Datalog revela la misma estructura en campos distintos.
- Ambos tienen objetos, consultan conjuntos de esos objetos y, a partir del resultado, aplican algo o producen hechos nuevos.
- Datalog y Prolog aparecieron desde los años 70 en bases de datos relacionales e investigación de IA de la época, y desde entonces se han reinventado de varias formas.
- Datomic
- Differential Datalog
- También se mencionan varios rule engines.
- El texto plantea la observación de que, si se construye un sistema donde hay objetos, se pueden describir conjuntos y se pueden realizar acciones sobre ellos, ese sistema tiende a converger hacia ideas parecidas.
- Los campos de bases de datos, programación lógica y desarrollo frontend web no suelen cruzarse tanto entre sí.
- Se añade la expectativa de que, si se conectaran más, podrían surgir cosas nuevas.
Container Queries y el límite del CSS real
- Esta discusión también se conecta con una función real de CSS: Container Queries.
- Permiten aplicar estilos al elemento actual según el estilo de su padre o sus ancestros.
- El ejemplo mostrado es
@container style(--theme: dark) { .card { background: royalblue; color: white; } }.
- Pero el problema del modo oscuro transitivo requiere un cálculo más fuerte que una simple consulta a ancestros.
- Cada elemento necesita saber si él mismo es effectively dark.
- Ese estado debe propagarse transitivamente a sus descendientes.
- Y la propagación debe detenerse al llegar a un límite
data-theme="light".
- Las container queries no pueden leer estado derivado.
- Pueden consultar el valor de propiedades personalizadas en ancestros, pero no un estado como
effectively-darkya calculado por otras reglas. - Solo pueden leer estado que ya existe en el DOM, no resultados de un cálculo recursivo.
- Pueden consultar el valor de propiedades personalizadas en ancestros, pero no un estado como
- Por eso, una consulta como “aplicar si cualquier ancestro es dark de forma transitiva y no hay un ancestro light más cercano” requiere recursión y no puede implementarse.
- El texto dice explícitamente que las container queries no tienen recursión.
- Un artículo de 2015 explica por qué las element queries siguieron fallando por razones parecidas.
- Si una consulta pudiera volver a consultar la propiedad que ella misma establece, aparecerían loops e incluso loops infinitos.
- El CSS Working Group ha resuelto este tipo de problemas limitando la dirección del flujo de información.
- Los descendientes pueden consultar información de ancestros, pero no al revés.
- Así se mantiene la finitud sin necesitar semántica de fixpoint.
- La información solo se propaga hacia abajo en el árbol y sin introducir nuevos hechos base.
- El texto describe esta evolución como un caso en que CSS se acerca a un motor tipo Datalog pero deliberadamente no llega hasta ahí.
- CSSLog permitiría ciclos y evaluación hasta el fixpoint.
- El CSS real se detiene antes porque un navegador es un motor de renderizado, no un motor incremental de base de datos relacional.
Posibilidades en otra dirección
- En vez de meter semántica de Datalog al navegador, también sería posible montar una sintaxis tipo CSS sobre Datalog.
- La sintaxis de Datalog con
:-, puntos, convenciones de mayúsculas y minúsculas y ausencia de asignaciones puede ser una barrera de entrada para usuarios modernos.
- La sintaxis de Datalog con
- CSS ya tiene una sintaxis que manipula estructuras de árbol de forma directa.
- Tiene combinadores de descendiente, hijo y hermanos, así que expresar relaciones padre-hijo resulta natural.
- En Datalog común, eso suele requerir una codificación relacional algo más incómoda.
- Se enfatiza que muchos datos reales tienen forma de árbol.
- JSON
- AST
- sistemas de archivos
- organigramas
- XML
- Si existiera una herramienta que combinara recursión por fixpoint, sintaxis estilo CSS y relaciones implícitas padre-hijo, sería posible escribir consultas recursivas sobre árboles con una notación más familiar.
- Según el texto, todavía nadie parece haber construido bien algo así.
- Cierra diciendo que alguien podría crear algo como “CSSLog”, pero con un nombre mejor.
Notas al pie
- Hay una nota sobre la simplificación de los elementos HTML.
- Se anticipa la objeción de que CSS no siempre trabaja solo con elementos HTML, pero el texto usa ese modelo para simplificar la explicación.
- La naive evaluation es ineficiente porque vuelve a calcular incluso hechos que ya se conocen.
- Como mejora estándar se menciona la semi-naive evaluation.
- La idea clave es que en cada paso solo se miran los hechos recién derivados.
- También se aclara que los loops infinitos no son algo extraño en lenguajes Turing-complete.
- En JavaScript también se puede escribir
while true {}. - Pero el contexto es que no queremos que el sistema de renderizado del navegador se quede bloqueado para siempre por una confusión lógica en un sitio web.
- En JavaScript también se puede escribir
- Otra nota al pie trata el rodeo mediante herencia de custom properties en CSS.
[data-theme="dark"] { --effective-theme: dark; }[data-theme="light"] { --effective-theme: light; }@container style(--effective-theme: dark) { :focus { outline-color: white; } }- Este enfoque funciona más o menos en este caso concreto, pero la herencia no equivale realmente a un cierre transitivo.
- En problemas más complejos que requieren cierre transitivo a través de cadenas de propiedades distintas de la relación padre-hijo, deja de funcionar.
1 comentarios
Comentarios en Hacker News
Los selectores de CSS son mucho más fáciles de escribir que XPath
Hace poco incluso hubo una presentación sobre cómo la nueva API de DOM de PHP permite trabajar con HTML y selectores CSS de forma nativa y muy sencilla. Antes había que convertir CSS a XPath.
[1] https://speakerdeck.com/keyvan/parsing-html-with-php-8-dot-4...
Como ha evolucionado con foco en el estilado del navegador, se extrañan funciones como la selección basada en contenido de texto que sí tiene XPath.
Entiendo que antes hubo propuestas, pero no entraron en la especificación porque podían causar problemas de rendimiento en el contexto de renderizado del navegador.
Mientras construía un agente de edición de documentos, mostraba el documento como HTML y hacía que el LLM indicara solo el selector CSS para traer al contexto los fragmentos necesarios; funcionaba casi como magia.
La gente puede usar exactamente la forma a la que ya está acostumbrada.
Ojalá hubiera un nombre para separar la sintaxis de CSS de todo el sistema completo de reglas, funciones y unidades que define el CSSWG.
Aquí hay bastante potencial, pero para hablar o investigar otros casos de uso parece que al final no queda más que hurgar en código de GitHub que incluya parsers de CSS para ver qué cosas rarísimas está construyendo la gente.
También he estado jugueteando con algo parecido a un motor de plantillas extraño, que mezcla un lenguaje de marcado ligero basado en nodos, selectores CSS para expresar qué entra en una plantilla, y una sintaxis parecida a CSS para controlar cómo se combinan estas piezas.
https://www.w3.org/TR/selectors-3/
La especificación de DOM también hace referencia a eso.
https://dom.spec.whatwg.org/#selectors
Así que el término general selector CSS ya es correcto, e incluso se le puede llamar simplemente selector.
El nombre selector DOM podría verse más limpio, pero si piensas en selectores usados en CSS estático o en otros motores DOM fuera de motores de JS, como parsers XML o la API DOM de PHP, en realidad podría resultar más confuso.
Además, existen selectores especiales directamente ligados al renderizado o la navegación del navegador, como
:hovero::target-text.Aun así, podría ser útil tener un nombre aparte para un subconjunto mínimo de sintaxis de consulta menos acoplado al navegador o a CSS.
Me hizo recordar https://github.com/braposo/graphql-css que vi hace tiempo en una conferencia.
Era un proyecto en broma, pero me gustó porque mostraba bien cómo trasplantar y reutilizar patrones en otros contextos puede hacer posibles cosas inesperadas.
Justo ando intentando traer patrones de contextos distintos de esa misma manera.
Aunque la mayoría no llegue muy lejos, desde la sensibilidad hacker resulta bastante interesante.
pyastgrep, como se ve en https://pyastgrep.readthedocs.io/en/latest/ , permite usar selectores CSS para consultar sintaxis de Python.
El valor predeterminado es XPath, y por ejemplo se puede hacer
pyastgrep --css 'Call > func > Name#main'.Casi conecta exactamente con la dirección a la que quería apuntar.
No me queda claro qué escenario resuelve esto.
Incluso ahora ya puedes cambiar condicionalmente un padre según sus hijos. Por ejemplo,
pretiene padding predeterminado de 16px, y si su hijo directo escode, puedes ponerlo en 0 con&:has(> code).La conclusión no es tanto “hay que arreglar las limitaciones del CSS moderno”, sino más bien preguntarse si montar una sintaxis tipo CSS sobre un sistema tipo Datalog podría hacer que trabajar con datos en forma de árbol resultara más familiar para más ingenieros.
Es decir, se habla de añadir nuevos elementos hijo o atributos al DOM.
Los LLM actuales más bien no manejan bien CSS, así que dan ganas de probar esto precisamente para ver si así el LLM puede razonar de forma más simple.
No se me ocurre mucha utilidad práctica real, pero igual se ve genial.
Mmm... siento que esto es simplemente JQ.
Me gusta CSS hasta cierto punto, pero no me gusta que cada vez tenga más creep de complejidad.
Entiendo la lógica de que los lenguajes de programación se vuelvan más potentes que los no programáticos, pero en vez de seguir inflando HTML, CSS y JavaScript, preferiría que apareciera otra cosa que reemplace todo eso.
Tampoco termino de ver por qué hacen falta la mayoría de los nuevos elementos de HTML5, así que casi no los uso. Al final terminé pensando que muchos contenedores no son más que
divcon IDs únicos, e incluso he deseado que esos IDs tuvieran algo como alias para navegaciónhrefinterna.Cosas como
[data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }me toman demasiado tiempo de interpretar mentalmente, así que ya no me parecen elegantes ni simples.En cambio,
h2 { color: red; }sigue siendo simple.Una expresión como
ancestor(X, Y) :- parent(X, Y).ya me da pereza pensarla. ¿Qué se supone que es:-? Parece una carita feliz.Dejé de leer en
@container style(--theme: dark) { .card { background: royalblue; color: white; } }.Se siente raro que un estándar que antes funcionaba bien se vaya estropeando cada vez más con el tiempo.
Por ejemplo, si desglosas
[data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }en pseudocódigo al estilo inglés, se acerca a decir: si existe X condata-theme="dark"y su hijo Y tienedata-theme="light"y está enfocado, entonces ponoutline-colorde Y en black.Así que en estilo Datalog se podría escribir como
outline-color(Y, black) if data-theme(X, "dark") and parent(X, Y) and data-theme(Y, "light") and focused(Y).Sería como cambiar
:-porify las comas porand.Incluso se podría ir más lejos y escribir algo como
Y.outline_color := black if X.data-theme == dark and Y.parent == X and Y.data-theme == dark and Y.focused, para queattr(X, val)se vea como una especie de azúcar sintáctica tipo UFCS del estiloX.attr == val.Si quisieras que se viera más de la familia ALGOL, también podría ser algo como
forall Y { Y.outline_color := black if Y.data_theme == "dark" and Y.focused and Y.parent.data_theme == "light" }.Aquí introduces Y explícitamente y dejas implícito un join, así que parece más programación general, pero en realidad el motor Datalog sería el que ejecuta eficientemente este tipo de bucles cada vez que cambian las dependencias.