1 puntos por GN⁺ 23 일 전 | 1 comentarios | Compartir por WhatsApp
  • 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.awesome crea 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.awesome para crear una intersección
  • Las reglas CSS unen un selector y una declaración para establecer propiedades como color o font-size en 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, Y para 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
  • 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 a color(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 aparece data-theme="light", requiere una consulta transitiva
    • En CSS real solo se puede cubrir una parte con reglas como [data-theme="dark"] :focus y [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
  • 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
  • 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 ancestor expande paso a paso la relación de ancestro a partir de la relación de parentesco
    • A partir de parent(alice, bob), primero aparece ancestor(alice, bob)
    • Luego también se derivan rutas como alice -> bob -> carol y alice -> bob -> dave
  • Este cálculo avanza hasta el final mediante evaluación de punto fijo, incluso sin un bucle for explí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; } }
  • 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
  • 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

 
GN⁺ 23 일 전
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.

    • Los LLM también manejan bastante bien los selectores CSS
      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.
    • Del lado del cliente, querySelector/querySelectorAll ya se usa tanto que da gusto ver que ahora también llegó al nuevo DOM de PHP.
      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.

    • Yo diría que en los estándares ya está bastante claramente separado.
      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 :hover o ::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.

    • Está divertido esto.
      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'.

    • Esto sí que está muy bueno.
      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, pre tiene padding predeterminado de 16px, y si su hijo directo es code, puedes ponerlo en 0 con &:has(> code).

    • En realidad, esto empezó más bien porque dos ideas distintas parecían parecerse al principio, y fui empujando esa conexión en varias direcciones.
      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.
    • De lo que se habla aquí no es de resolverlo con un solo cálculo de estilos, sino de una sintaxis que modifica los datos subyacentes mismos de los objetivos que coinciden con el selector.
      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 div con IDs únicos, e incluso he deseado que esos IDs tuvieran algo como alias para navegación href interna.
    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.

    • Mi intención no va tanto por sumarle más sintaxis y significado a CSS, sino más bien por robar ideas de CSS y aprovechar la similitud con los lenguajes de consulta lógicos/relacionales para crear algo nuevo.
      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 con data-theme="dark" y su hijo Y tiene data-theme="light" y está enfocado, entonces pon outline-color de 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 :- por if y las comas por and.
      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 que attr(X, val) se vea como una especie de azúcar sintáctica tipo UFCS del estilo X.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.