1 puntos por GN⁺ 4 일 전 | 1 comentarios | Compartir por WhatsApp
  • 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-dark pueda 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, a o div ya existen fuera de CSS, y CSS no los declara de nuevo.
    • Como ejemplo se mencionan elementos con atributos como class, id y data-custom-attribute.
  • Los selectores CSS apuntan a conjuntos con condiciones en común, y pueden restringir el objetivo por nombre de etiqueta, id, class o valor de atributo.
    • Aparecen como ejemplos selectores como div, #child, .awesome y [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.
  • Un selector combinado como div.awesome realiza una intersección de conjuntos, seleccionando solo elementos que sean div y 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 definen color y font-size para esos elementos.
    • En el navegador, el resultado sería texto rojo y grande.

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: blue a elementos con color: red tendría una semántica ambigua.
  • 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.
  • 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.
  • 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: +bar para 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.
  • Después de ejecutar una regla div.foo, el mismo elemento podría pasar a hacer match con div.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; }.
  • 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 alice y bob pueden usarse sin declaración previa.
    • El texto los compara con los :symbols de Ruby.
  • Los conjuntos y relaciones se expresan con relations y tuples.
    • parent(alice, bob) es una tupla dentro de la relación parent.
    • parent se 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 los X de los que Bob es padre.
    • En el ejemplo, X se evalúa como carol y dave.
    • 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 ancestor se 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 ancestor aparece tanto en el head como en el body.
    • Gracias a eso se derivan relaciones indirectas como alice -> bob -> carol y alice -> bob -> dave.
  • 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 for explí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 ancestor se resume en tres rondas.
    • En la primera, los hechos parent agregan 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.
  • 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.

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.
  • 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-dark ya calculado por otras reglas.
    • Solo pueden leer estado que ya existe en el DOM, no resultados de un cálculo recursivo.
  • 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.
  • 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.
  • 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

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