1 points par GN⁺ 4 일 전 | 1 commentaires | Partager sur WhatsApp
  • La structure de CSS, qui sélectionne un ensemble cible avec des sélecteurs et des règles puis lui applique des propriétés, ressemble formellement à Datalog, qui fonctionne avec des ensembles et des règles
  • La combinaison de sélecteurs comme div.awesome crée une intersection et, en Datalog, un join similaire se produit en répétant la même variable
  • Le CSS actuel ne peut pas réutiliser le résultat d’un style calculé comme condition de sélection, ce qui rend difficile l’expression directe de requêtes transitives récursives ou de la propagation répétée d’états dérivés
  • Datalog étend les relations à l’aide de règles récursives et d’une évaluation au point fixe jusqu’à ce qu’aucun nouveau fait n’apparaisse, et grâce à la monotonie, le calcul peut se terminer sur un domaine fini
  • Le CSS réel peut lire des informations sur les ancêtres avec des fonctionnalités comme les Container Queries, mais il a choisi d’éviter les boucles de rétroaction et les cycles, tout en laissant ouverte la possibilité d’appliquer la syntaxe CSS à des requêtes récursives

La ressemblance structurelle entre CSS et Datalog

  • CSS a une structure fondée sur la sélection d’un ensemble cible et l’application de règles aux éléments sélectionnés
    • Des « Things » comme les éléments HTML existent d’abord, puis un selector désigne un ensemble partageant des propriétés communes
    • On peut décrire des ensembles avec des selector comme div, #child, .awesome, [data-custom-attribute="foo"]
    • On peut combiner des selector, comme dans div.awesome, pour former une intersection
  • Une règle CSS associe un selector et des declarations pour définir sur les éléments sélectionnés des propriétés comme color ou font-size
    • Mais ces propriétés modifient généralement un état extérieur au langage et leur résultat ne peut pas ensuite servir de condition de selector
    • Une forme comme div[color=red], qui requerrait à nouveau le résultat du style, n’est pas acceptée par le navigateur
  • Datalog fonctionne de manière similaire avec des ensembles de faits et une déduction fondée sur des règles
    • Des atomes et relations comme parent(alice, bob) en sont l’unité de base
    • On peut utiliser des variables X, Y pour sélectionner des ensembles d’éléments correspondant à des conditions
    • Répéter la même variable pour relier des conditions produit un join proche de la combinaison de selector en CSS
  • La forme head(X, Y) :- body1(X, Z), body2(Z, Y) ressemble à une règle CSS, avec simplement la direction inversée
    • Le selector en CSS est proche du body en Datalog, et la declaration est proche du head
    • div.awesome { color: red; } correspond à color(X, red) :- div(X), class(X, awesome).

Les requêtes récursives que CSS ne sait pas faire

  • La condition consistant à appliquer un style inversé à tous les éléments focalisés à l’intérieur de data-theme="dark", tout en s’arrêtant si data-theme="light" apparaît au milieu, nécessite une requête transitive
    • En CSS réel, on ne peut traiter qu’une partie du problème avec des règles comme [data-theme="dark"] :focus et [data-theme="dark"] [data-theme="light"] :focus
    • Quand le niveau d’imbrication augmente, il faut continuer à ajouter des règles, et il devient difficile d’exprimer directement la relation récursive
  • La condition nécessaire consiste à déterminer récursivement si un élément est effectively-dark
    • S’il a lui-même data-theme="dark", il devient effectively-dark
    • Un enfant sous un ancêtre effectively-dark devient lui aussi effectively-dark s’il n’y a pas de data-theme="light" entre les deux
    • C’est sur la base de cet état qu’il faut appliquer un style à .effectively-dark :focus
  • Dans une syntaxe hypothétique CSSLog, une règle pourrait ajouter un état dérivé comme class: +effectively-dark
    • .effectively-dark > :not([data-theme="light"]) propage l’état aux enfants
    • Les règles devraient être répétées récursivement jusqu’à atteindre l’état cible
  • Ce type de propagation récursive est difficile à exprimer avec le CSS actuel
    • La fin de l’article montre quelques méthodes pour l’imiter partiellement, mais ce n’est pas une solution générale fondée sur le même principe

La récursion et le point fixe en Datalog

  • Datalog fonctionne en déduisant de nouveaux faits à partir des faits existants, et traite nativement la récursion
    • ancestor(X, Y) :- parent(X, Y).
    • ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
  • La règle ancestor étend progressivement la relation d’ascendance à partir de la relation parent
    • À partir de parent(alice, bob), on obtient d’abord ancestor(alice, bob)
    • Puis on déduit aussi des chemins comme alice -> bob -> carol et alice -> bob -> dave
  • Ce calcul va jusqu’au bout grâce à une évaluation au point fixe, sans boucle for explicite
    • Au départ, on n’utilise que les faits de base explicitement déclarés
    • On applique le body de toutes les règles à l’ensemble courant de faits pour ajouter le head
    • On s’arrête quand aucun nouveau fait n’apparaît
  • Si cette approche se termine, c’est grâce à la monotonie
    • On n’ajoute que des faits, on n’en retire pas, donc l’ensemble des faits connus ne peut que croître
    • Si l’on part d’un ensemble fini de faits, le nombre de faits déductibles reste lui aussi borné
    • À l’inverse, si l’on pouvait retirer des faits, des conclusions précédentes pourraient être invalidées et provoquer une boucle infinie

Container Queries et les limites du CSS réel

  • Les Container Queries du CSS réel permettent d’appliquer des règles en fonction du style d’un ancêtre ou d’un conteneur
    • Une forme comme @container style(--theme: dark) { .card { background: royalblue; color: white; } } est prise en charge
  • Mais l’exemple du mode sombre transitif exige des conditions plus fortes qu’une simple lecture d’ancêtre
    • Chaque élément doit savoir s’il est lui-même effectively-dark
    • Cet état doit être propagé transitivement à l’ensemble de ses descendants
    • La propagation doit s’arrêter à la frontière data-theme="light"
  • Les Container Queries ne peuvent pas traiter la deuxième condition
    • On peut lire une custom property héritée d’un ancêtre, mais on ne peut pas requerir à nouveau un état dérivé déjà calculé par une autre règle
    • On peut voir les informations présentes à l’origine dans le DOM, mais pas utiliser le résultat d’un calcul récursif comme condition de selector
  • Un article lié de 2015 notait déjà que les element queries se heurtaient au même problème
    • Si une propriété définie par une requête peut elle-même être à nouveau requêtée, le risque de boucles et de répétitions infinies augmente
  • Le CSS Working Group a évité ce problème en limitant la direction du flux d’information
    • Il autorise un descendant à requêter des informations sur ses ancêtres
    • Il bloque la rétroaction dans le sens inverse ou les cycles impliquant le style de l’élément lui-même
    • Ainsi, le calcul reste fini sans avoir besoin d’une sémantique de point fixe

La possibilité d’inverser la syntaxe CSS en langage de requête récursif

  • Plutôt que d’injecter la sémantique de Datalog dans CSS, une piste plus réaliste serait de poser la syntaxe CSS au-dessus de Datalog
    • La syntaxe de Datalog, avec :-, les points et les atomes sans déclaration, constitue une barrière d’entrée importante pour les utilisateurs de langages modernes
    • CSS dispose déjà d’une syntaxe de selector riche pour manipuler des structures arborescentes
  • De nombreuses données réelles ont justement une forme arborescente
    • JSON
    • AST
    • système de fichiers
    • organigramme
    • XML
  • Dans ces domaines, combiner une syntaxe de type CSS qui traite implicitement les relations parent/enfant avec une récursion au point fixe pourrait être utile
    • En Datalog classique, il faut réécrire les structures arborescentes sous forme relationnelle, ce qui est assez lourd
    • Si l’on réutilise directement l’intuition des selector CSS pour des requêtes récursives, davantage de programmeurs pourraient s’en emparer facilement
  • Ce type d’outil n’existe pas encore vraiment de manière nette
    • Le nom « CSSLog » n’est que provisoire, et un langage mieux nommé pourrait apparaître
    • Il reste de la place pour traiter les requêtes récursives sur des arbres avec une notation plus familière

Points complémentaires et liens de référence

  • Datalog est apparu dans les années 1970 dans le contexte des bases de données relationnelles et de la recherche en IA de l’époque, puis a réémergé à plusieurs reprises sous différentes formes
  • Une forme simple de calcul au point fixe est présentée sous le nom de naive evaluation, mais elle peut être inefficace car elle recalcule sans cesse les faits déjà connus
    • Une amélioration classique mentionnée ici est la semi-naive evaluation, qui n’utilise à chaque étape que les faits nouvellement produits
  • La monotonie est aussi une propriété utile dans les systèmes distribués
  • Il existe aussi une manière d’imiter partiellement le mode sombre transitif via l’héritage des custom properties
    • [data-theme="dark"] { --effective-theme: dark; }
    • [data-theme="light"] { --effective-theme: light; }
    • @container style(--effective-theme: dark) { :focus { outline-color: white; } }
    • Cette méthode fonctionne globalement pour ce cas précis, mais ne fournit pas en général une véritable fermeture transitive

1 commentaires

 
GN⁺ 4 일 전
Commentaires sur Hacker News
  • Les sélecteurs CSS sont bien plus faciles à écrire que XPath
    Il y a eu récemment une présentation expliquant que la nouvelle API DOM de PHP permet de manipuler HTML et les sélecteurs CSS de manière native et très simplement. Avant, il fallait convertir le CSS en XPath
    [1] https://speakerdeck.com/keyvan/parsing-html-with-php-8-dot-4...
    C’est dommage que, comme tout cela s’est développé autour du stylage dans le navigateur, il manque des fonctionnalités comme la sélection basée sur le contenu textuel, présente dans XPath
    Je crois qu’il y avait eu une proposition autrefois, mais qu’elle n’est pas entrée dans la spec à cause de problèmes de performances dans le contexte du rendu navigateur

    • Les LLM se débrouillent aussi plutôt bien avec les sélecteurs CSS
      En construisant un agent d’édition de documents, j’affichais le document en HTML et je laissais le LLM indiquer uniquement un sélecteur CSS pour récupérer les fragments nécessaires dans le contexte, et ça marchait presque comme par magie
    • Côté client, querySelector/querySelectorAll sont tellement largement utilisés que je suis content de les voir arriver aussi dans le nouveau DOM de PHP
      Les gens peuvent utiliser une approche qui leur est déjà familière
  • J’aimerais qu’il existe un nom distinct pour séparer la syntaxe CSS de tout l’ensemble des règles, fonctions et unités définies par le CSSWG
    Il y a pas mal de potentiel ici, mais pour parler d’autres cas d’usage ou enquêter dessus, on dirait qu’il faut au final fouiller du code sur GitHub intégrant des parseurs CSS pour voir quelles choses étranges les gens fabriquent
    Je bricole aussi une sorte d’étrange moteur de templates, mélangeant un langage de balisage léger basé sur des nœuds, des sélecteurs CSS pour exprimer ce qui doit entrer dans les templates, et une syntaxe proche de CSS pour contrôler comment assembler ces morceaux

    • À mon avis, dans les standards, la séparation est déjà assez claire
      https://www.w3.org/TR/selectors-3/
      La spec DOM y fait aussi référence
      https://dom.spec.whatwg.org/#selectors
      Donc l’appellation générique sélecteur CSS est déjà correcte, et on peut aussi simplement dire sélecteur
      Le nom sélecteur DOM serait peut-être plus net, mais si on pense aussi aux sélecteurs utilisés dans le CSS statique ou dans d’autres moteurs DOM hors moteur JS (parseur XML, API DOM PHP, etc.), cela risque au contraire d’être plus confus
      Il existe aussi des sélecteurs spéciaux directement liés au rendu ou à la navigation dans le navigateur, comme :hover ou ::target-text
      Cela dit, un nom séparé pourrait être utile pour un sous-ensemble minimal de syntaxe de requête moins couplé au navigateur ou à CSS
  • Ça me rappelle https://github.com/braposo/graphql-css que j’avais vu à une conférence il y a quelque temps
    C’était un projet pour rire, mais je l’avais bien aimé parce qu’il montrait bien comment le fait de transplanter un pattern dans un autre contexte et de le réutiliser peut rendre possibles des choses inattendues

    • C’est amusant, ça
      C’est justement ce genre de réutilisation de patterns entre contextes différents que j’essaie d’explorer
      Même si la plupart de ces idées ne vont pas très loin, ça reste assez intéressant dans un esprit hacker
  • pyastgrep, comme on peut le voir sur https://pyastgrep.readthedocs.io/en/latest/, permet d’utiliser des sélecteurs CSS pour interroger la syntaxe Python
    XPath est la valeur par défaut, mais on peut par exemple faire pyastgrep --css 'Call > func > Name#main'

    • C’est vraiment excellent
      C’est presque exactement la direction que je voulais désigner
  • Je ne vois pas très bien quel scénario cela résout
    Aujourd’hui déjà, on peut modifier conditionnellement un parent selon ses enfants. Par exemple, pre a un padding par défaut de 16px et, s’il a code comme enfant direct, on peut le mettre à 0 avec &:has(> code)

    • En fait, au départ, cela venait plutôt du fait que deux idées différentes me semblaient se ressembler, puis j’ai surtout cherché à pousser ce lien dans plusieurs directions
      La conclusion est moins « il faut corriger les limites du CSS moderne » que « si on appliquait une syntaxe proche de CSS à un système proche de Datalog, on pourrait peut-être rendre le traitement de données arborescentes plus familier pour davantage d’ingénieurs »
    • Ce dont on parle ici n’est pas une résolution en un seul calcul de style, mais une syntaxe qui modifie les données sous-jacentes elles-mêmes des éléments correspondant aux sélecteurs
      Autrement dit, il s’agit d’ajouter de nouveaux éléments enfants ou attributs au DOM
  • Les LLM actuels ont plutôt du mal avec CSS, donc ça me donne au contraire envie d’essayer ça pour voir si cela permettrait aux LLM de raisonner plus simplement

  • Je ne vois pas très bien l’utilité concrète, mais c’est quand même cool

  • Hum... j’ai l’impression que ce n’est juste que JQ

  • J’aime assez CSS jusqu’à un certain point, mais je déteste la dérive de complexité qui ne cesse de s’aggraver
    Je comprends l’argument selon lequel les langages de programmation deviennent plus puissants que les non-langages de programmation, mais au lieu de continuer à complexifier HTML, CSS et JavaScript, j’ai plutôt l’impression qu’il vaudrait mieux voir apparaître autre chose pour remplacer l’ensemble
    Je n’utilise presque jamais non plus les nouveaux éléments de HTML5, parce que je ne vois pas vraiment pourquoi la plupart sont nécessaires. J’en suis venu à penser qu’au fond beaucoup de conteneurs ne sont que des div avec un ID unique, et j’aurais même aimé qu’il existe une sorte d’alias pour ces ID afin de naviguer avec href dans les liens internes
    Des trucs comme [data-theme="dark"] [data-theme="light"] :focus { outline-color: black; } me prennent bien trop de temps à interpréter mentalement, au point que ça ne me paraît plus élégant ni simple
    En revanche, h2 { color: red; } reste simple
    Une expression comme ancestor(X, Y) :- parent(X, Y). me donne déjà envie de ne plus y penser. C’est quoi, :- ? On dirait un visage qui sourit
    Je me suis arrêté de lire à @container style(--theme: dark) { .card { background: royalblue; color: white; } }
    C’est étrange de voir un standard qui fonctionnait bien autrefois sembler se dégrader avec le temps

    • Mon intention n’est pas tant d’ajouter de la syntaxe et de la sémantique à CSS que de voler des idées à CSS pour exploiter sa parenté avec les langages de requête logiques/relationnels afin de créer quelque chose de nouveau
      Par exemple, si on développe [data-theme="dark"] [data-theme="light"] :focus { outline-color: black; } en pseudo-code de type anglais, cela revient à peu près à dire : s’il existe un X avec data-theme="dark" et que son enfant Y a data-theme="light" et est dans l’état focus, alors mettre outline-color de Y à black
      On peut donc l’écrire à la manière de Datalog comme outline-color(Y, black) if data-theme(X, "dark") and parent(X, Y) and data-theme(Y, "light") and focused(Y)
      En gros, on remplace :- par if et les virgules par and
      On peut aller plus loin et écrire quelque chose comme Y.outline_color := black if X.data-theme == dark and Y.parent == X and Y.data-theme == dark and Y.focused, pour faire apparaître attr(X, val) comme une sorte de sucre syntaxique proche de l’UFCS tel que X.attr == val
      Si on veut quelque chose qui ressemble davantage à la famille ALGOL, on pourrait aussi écrire forall Y { Y.outline_color := black if Y.data_theme == "dark" and Y.focused and Y.parent.data_theme == "light" }
      Ici, Y est introduit explicitement et une jointure est implicite, ce qui donne une apparence plus proche de la programmation générale, mais en réalité c’est toujours le moteur Datalog qui exécute efficacement ce type de boucle à chaque changement de dépendance