Ver CSS como un lenguaje de consultas
(evdc.me)- La estructura de CSS, que elige un conjunto objetivo con selectores y reglas y aplica propiedades, se parece en su forma a Datalog, que opera con conjuntos y reglas
- La combinación de selectores como
div.awesomecrea una intersección, y en Datalog ocurre un join similar repitiendo la misma variable - El CSS actual no puede volver a usar el resultado de los estilos calculados como condición de selección, por lo que es difícil expresar directamente consultas transitivas recursivas o la propagación repetida de estados derivados
- Datalog expande relaciones con reglas recursivas y evaluación de punto fijo hasta que ya no aparecen hechos nuevos, y gracias a la monotonicidad puede terminar el cálculo dentro de un alcance finito
- El CSS real puede leer información de ancestros con funciones como Container Queries, pero ha optado por evitar los bucles de retroalimentación y las circularidades, dejando aun así espacio para aplicar la sintaxis de CSS a consultas recursivas
La estructura parecida entre CSS y Datalog
- CSS tiene una estructura de selección de conjuntos objetivo y aplicación de reglas a los elementos seleccionados
- Primero existen “Things”, como los elementos HTML, y el selector apunta a un conjunto con propiedades en común
- Se puede describir un conjunto con selectores como
div,#child,.awesome,[data-custom-attribute="foo"] - Se pueden combinar selectores como
div.awesomepara crear una intersección
- Las reglas CSS unen un selector y una declaración para establecer propiedades como
colorofont-sizeen los elementos seleccionados- Sin embargo, estas propiedades por lo general cambian un estado fuera del lenguaje, y su resultado no puede volver a usarse como condición del selector
- El navegador no acepta una forma como
div[color=red], donde se vuelve a consultar el resultado del estilo
- Datalog funciona de forma parecida con conjuntos de hechos y derivación basada en reglas
- Átomos y relaciones como
parent(alice, bob)son la unidad básica - Se pueden usar variables
X,Ypara elegir el conjunto de elementos que cumplen una condición - Si se repite la misma variable para enlazar condiciones, ocurre un join parecido a la combinación de selectores en CSS
- Átomos y relaciones como
- La estructura
head(X, Y) :- body1(X, Z), body2(Z, Y)tiene una forma similar a la de una regla CSS, aunque con la dirección invertida- El selector de CSS se parece más al body de Datalog, y la declaración se parece más al head
div.awesome { color: red; }corresponde acolor(X, red) :- div(X), class(X, awesome).
Las consultas recursivas que CSS no puede hacer
- La condición de dar un estilo invertido a todos los elementos con foco dentro de
data-theme="dark", pero detenerse si en medio aparecedata-theme="light", requiere una consulta transitiva- En CSS real solo se puede cubrir una parte con reglas como
[data-theme="dark"] :focusy[data-theme="dark"] [data-theme="light"] :focus - Si aumenta el nivel de anidación, hay que seguir agregando reglas, y es difícil expresar directamente una relación recursiva
- En CSS real solo se puede cubrir una parte con reglas como
- La condición necesaria es determinar recursivamente si un elemento es effectively-dark
- Si él mismo tiene
data-theme="dark", pasa a ser effectively-dark - Un hijo bajo un ancestro effectively-dark también lo será, siempre que no haya
data-theme="light"en medio - Con base en ese estado, habría que aplicar estilos a
.effectively-dark :focus
- Si él mismo tiene
- En una sintaxis hipotética de CSSLog, una regla podría agregar un estado derivado como
class: +effectively-dark.effectively-dark > :not([data-theme="light"])propaga ese estado al hijo- La regla tendría que repetirse recursivamente hasta llegar al estado objetivo
- Este tipo de propagación recursiva es difícil de expresar con el CSS actual
- Al final del artículo también aparecen formas de imitar algo parecido, pero no son una solución general basada en el mismo principio
Recursión y punto fijo en Datalog
- Datalog funciona derivando hechos nuevos a partir de hechos existentes, y maneja la recursión de forma nativa
ancestor(X, Y) :- parent(X, Y).ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
- La regla
ancestorexpande paso a paso la relación de ancestro a partir de la relación de parentesco- A partir de
parent(alice, bob), primero apareceancestor(alice, bob) - Luego también se derivan rutas como
alice -> bob -> carolyalice -> bob -> dave
- A partir de
- Este cálculo avanza hasta el final mediante evaluación de punto fijo, incluso sin un bucle
forexplícito- Al principio solo se usan los hechos base declarados
- Se sustituyen los body de todas las reglas sobre el conjunto actual de hechos y se agregan los head
- Se detiene cuando ya no aparecen hechos nuevos
- La razón por la que este enfoque termina está en la monotonicidad
- Solo se agregan hechos y no se eliminan, así que el conjunto de hechos conocidos no hace más que crecer
- Si se parte de un conjunto finito de hechos, la cantidad de hechos derivables también queda acotada de forma finita
- En cambio, si se pudieran eliminar hechos, conclusiones previas podrían revertirse y caer en un bucle infinito
Container Queries y los límites del CSS real
- Las Container Queries de CSS real permiten aplicar reglas con base en el estilo de un ancestro o contenedor
- Admiten formas como
@container style(--theme: dark) { .card { background: royalblue; color: white; } }
- Admiten formas como
- Pero el ejemplo del modo oscuro transitivo exige una condición más fuerte que una simple consulta al ancestro
- Cada elemento necesita saber si él mismo es effectively-dark
- Ese estado debe propagarse transitivamente a todos los descendientes
- La propagación debe detenerse en el límite marcado por
data-theme="light"
- Container Queries no pueden resolver la segunda condición
- Aunque pueden leer una custom property del ancestro, no pueden volver a consultar un estado derivado que otra regla ya calculó
- Pueden ver información originalmente presente en el DOM, pero no usar el resultado de un cálculo recursivo como condición del selector
- Un artículo relacionado de 2015 también señala que las element queries chocaban con el mismo problema
- Si una propiedad establecida por una consulta pudiera volver a consultarse, crecería el riesgo de bucles e iteraciones infinitas
- El CSS Working Group ha evitado este problema limitando la dirección del flujo de información
- Permite que los descendientes consulten información de los ancestros
- Bloquea la retroalimentación en dirección contraria o los ciclos sobre el estilo del propio elemento
- Así mantiene el cálculo finito sin necesidad de una semántica de punto fijo
La posibilidad de invertir la sintaxis de CSS hacia un lenguaje de consultas recursivas
- Más que meter la semántica de Datalog dentro de CSS, se propone como camino más realista montar la sintaxis de CSS sobre Datalog
- La sintaxis de Datalog, con
:-, puntos y átomos sin declaración, es una barrera de entrada alta para quienes usan lenguajes modernos - CSS ya tiene una sintaxis rica de selectores para tratar con estructuras en árbol
- La sintaxis de Datalog, con
- Se señala que muchos datos reales tienen forma de árbol
- JSON
- AST
- sistemas de archivos
- organigramas
- XML
- En estos ámbitos, combinar una sintaxis estilo CSS, que trata implícitamente relaciones padre/hijo, con recursión por punto fijo podría ser útil
- En Datalog general, hay que reescribir la estructura de árbol en una representación relacional, lo que resulta bastante incómodo
- Si la intuición de los selectores CSS se lleva tal cual a las consultas recursivas, más programadores podrían acceder a ello con facilidad
- Todavía no se ve con claridad una herramienta de este tipo
- El nombre “CSSLog” es provisional, y podría aparecer un lenguaje con un mejor nombre
- Sigue habiendo espacio para tratar consultas recursivas sobre árboles con una notación más familiar
Puntos complementarios y enlaces de referencia
- Datalog surgió desde los años 1970 en el contexto de las bases de datos relacionales y la investigación en IA de esa época, y después volvió a aparecer repetidamente en varias formas
- Una forma simple del cálculo de punto fijo se presenta como naive evaluation, pero puede ser ineficiente porque recalcula una y otra vez hechos ya conocidos
- También se menciona como mejora representativa la semi-naive evaluation, que aprovecha solo los hechos nuevos de cada etapa
- La monotonicidad también lleva a propiedades útiles en sistemas distribuidos
- También hay una forma de imitar parcialmente el modo oscuro transitivo con herencia de custom properties
[data-theme="dark"] { --effective-theme: dark; }[data-theme="light"] { --effective-theme: light; }@container style(--effective-theme: dark) { :focus { outline-color: white; } }- Este método suele funcionar en este caso concreto, pero no ofrece de forma general un verdadero cierre transitivo
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.