1 pontos por GN⁺ 15 일 전 | 1 comentários | Compartilhar no WhatsApp
  • A estrutura do CSS, que seleciona um conjunto-alvo com seletores e regras e aplica propriedades, se parece em forma com o Datalog, que opera com conjuntos e regras
  • A combinação de seletores como div.awesome cria uma interseção, e no Datalog ocorre um join parecido ao repetir a mesma variável
  • O CSS atual não consegue reutilizar o resultado do estilo calculado como condição de seleção, então é difícil expressar diretamente consultas transitivas recursivas ou a propagação iterativa de estados derivados
  • O Datalog expande relações com regras recursivas e avaliação por ponto fixo até que não surjam mais fatos novos e, graças à monotonicidade, consegue terminar o cálculo em um escopo finito
  • O CSS real consegue ler informações de ancestrais com recursos como Container Queries, mas escolheu evitar loops de feedback e ciclos, embora ainda exista espaço para incorporar consultas recursivas à sintaxe do CSS

Estruturas semelhantes entre CSS e Datalog

  • O CSS tem a estrutura de seleção de conjunto-alvo e aplicação de regras ao alvo selecionado
    • Primeiro existem “coisas” como elementos HTML, e o selector aponta para um conjunto com propriedades em comum
    • É possível descrever conjuntos com selectors como div, #child, .awesome, [data-custom-attribute="foo"]
    • É possível combinar selectors como div.awesome para criar uma interseção
  • As regras CSS unem selector e declaration para definir propriedades como color ou font-size nos elementos selecionados
    • Porém, essas propriedades em geral alteram um estado fora da linguagem, e o resultado não pode voltar a ser usado como condição em selectors
    • Uma forma como div[color=red], consultando novamente o resultado do estilo, não é aceita pelo navegador
  • O Datalog funciona de forma parecida, com conjuntos de fatos e derivação baseada em regras
    • Átomos e relations como parent(alice, bob) são a unidade básica
    • Variáveis X e Y permitem selecionar conjuntos de itens que atendem às condições
    • Ao repetir a mesma variável para ligar condições, ocorre um join parecido com a combinação de selectors no CSS
  • A estrutura head(X, Y) :- body1(X, Z), body2(Z, Y) é parecida com uma regra CSS, apenas com a direção invertida
    • O selector do CSS se aproxima do body do Datalog, e a declaration se aproxima do head
    • div.awesome { color: red; } corresponde a color(X, red) :- div(X), class(X, awesome).

As consultas recursivas que o CSS não consegue fazer

  • A condição de aplicar um estilo invertido a todos os elementos em foco dentro de data-theme="dark", mas parar quando aparecer data-theme="light" no meio do caminho, exige uma consulta transitiva
    • No CSS real, dá para cobrir só parte do caso com regras como [data-theme="dark"] :focus e [data-theme="dark"] [data-theme="light"] :focus
    • Se o nível de aninhamento aumenta, é preciso continuar adicionando regras, e fica difícil expressar diretamente a relação recursiva
  • A condição necessária é determinar recursivamente se um elemento é effectively-dark
    • Se ele próprio tiver data-theme="dark", então se torna effectively-dark
    • Filhos sob um ancestral effectively-dark também se tornam effectively-dark, desde que não haja data-theme="light" no meio
    • Com base nesse estado, seria preciso aplicar estilo a .effectively-dark :focus
  • Em uma sintaxe hipotética chamada CSSLog, as regras poderiam adicionar estados derivados com algo como class: +effectively-dark
    • .effectively-dark > :not([data-theme="light"]) propagaria o estado para os filhos
    • As regras precisariam se repetir recursivamente até alcançar o estado-alvo
  • Esse tipo de propagação recursiva é difícil de expressar no CSS atual
    • No fim do texto aparecem formas de imitar algo parecido, mas não são uma solução geral baseada no mesmo princípio

Recursão e ponto fixo no Datalog

  • O Datalog funciona derivando fatos novos a partir de fatos existentes e trata recursão como algo básico
    • ancestor(X, Y) :- parent(X, Y).
    • ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
  • As regras de ancestor expandem gradualmente a relação de ancestralidade com base na relação de paternidade
    • De parent(alice, bob) surge primeiro ancestor(alice, bob)
    • Depois também são derivados caminhos como alice -> bob -> carol e alice -> bob -> dave
  • Esse cálculo avança até o fim por meio de avaliação por ponto fixo, mesmo sem um loop for explícito
    • No começo, usa apenas os fatos-base declarados
    • O body de todas as regras é aplicado ao conjunto atual de fatos para acrescentar o head
    • O processo para quando não surgem mais fatos novos
  • O motivo de isso terminar está na monotonicidade
    • O sistema só adiciona fatos, sem removê-los, então o conjunto de fatos conhecidos só cresce
    • Se começa com um conjunto finito de fatos, o número de fatos deriváveis também fica finitamente limitado
    • Em contrapartida, se fosse possível remover fatos, conclusões anteriores poderiam ser revertidas e o processo poderia cair em loop infinito

Container Queries e os limites do CSS real

  • As Container Queries do CSS real permitem aplicar regras com base no estilo de um ancestral ou contêiner
    • Há suporte para formas como @container style(--theme: dark) { .card { background: royalblue; color: white; } }
  • Mas o exemplo de dark mode transitivo exige algo mais forte do que apenas consultar ancestrais
    • Cada elemento precisa saber se ele mesmo é effectively-dark
    • Esse estado precisa ser propagado de forma transitiva para todos os descendentes
    • A propagação precisa parar na fronteira data-theme="light"
  • Container Queries não conseguem tratar a segunda condição
    • Dá para ler custom properties de ancestrais, mas não consultar de volta um estado derivado já calculado por outras regras
    • É possível ver informações que já existiam no DOM, mas não usar o resultado de um cálculo recursivo como condição de selector
  • Um texto relacionado de 2015 também aponta que element queries esbarravam no mesmo problema
    • Se uma propriedade definida por uma consulta puder ser consultada novamente, cresce o risco de loops e repetição infinita
  • O CSS Working Group vem evitando esse problema por meio de restrições na direção do fluxo de informação
    • É permitido que descendentes consultem informações de ancestrais
    • Mas feedback no sentido oposto ou ciclos envolvendo o próprio estilo do elemento são bloqueados
    • Assim, o cálculo permanece finito mesmo sem semântica de ponto fixo

A possibilidade de inverter a sintaxe do CSS em uma linguagem de consulta recursiva

  • Em vez de colocar a semântica do Datalog dentro do CSS, o texto sugere como caminho mais realista colocar a sintaxe do CSS sobre o Datalog
    • A sintaxe do Datalog, com :-, pontos finais e átomos sem declaração, representa uma barreira de entrada para muitos usuários de linguagens modernas
    • O CSS já tem uma sintaxe rica de selectors para lidar com estruturas em árvore
  • O texto observa que muitos dados reais têm forma de árvore
    • JSON
    • AST
    • sistema de arquivos
    • organograma
    • XML
  • Nesses domínios, pode ser útil combinar sintaxe ao estilo CSS, que lida implicitamente com relações entre pai e filho, com recursão por ponto fixo
    • No Datalog comum, é trabalhoso reescrever a estrutura em árvore como representação relacional
    • Se a intuição dos selectors CSS puder ser levada diretamente para consultas recursivas, mais programadores poderão se aproximar disso com facilidade
  • Ainda não há uma ferramenta claramente estabelecida nesse formato
    • O nome “CSSLog” é provisório, e pode surgir uma linguagem melhor batizada
    • Ainda existe espaço para tratar consultas recursivas em árvores com uma notação mais familiar

Pontos complementares e links de referência

  • O Datalog surgiu nos anos 1970 no contexto de bancos de dados relacionais e da pesquisa em IA da época, e desde então reapareceu repetidamente em várias formas
  • Uma forma simples de cálculo de ponto fixo é apresentada como naive evaluation, mas ela pode ser ineficiente por recalcular repetidamente fatos já conhecidos
    • Como melhoria representativa, também é mencionada a semi-naive evaluation, que usa apenas os fatos novos de cada etapa
  • A monotonicidade também leva a propriedades úteis em sistemas distribuídos
  • Também existe uma forma de imitar parcialmente o dark mode transitivo com herança de custom properties
    • [data-theme="dark"] { --effective-theme: dark; }
    • [data-theme="light"] { --effective-theme: light; }
    • @container style(--effective-theme: dark) { :focus { outline-color: white; } }
    • Esse método em geral funciona para esse caso específico, mas não fornece um fechamento transitivo real de forma geral

1 comentários

 
GN⁺ 15 일 전
Comentários do Hacker News
  • Seletores CSS são muito mais fáceis de escrever do que XPath
    Recentemente até houve uma apresentação sobre como a nova API DOM do PHP lida com HTML e seletores CSS de forma nativa e muito simples. Antes, era preciso converter CSS para XPath
    [1] https://speakerdeck.com/keyvan/parsing-html-with-php-8-dot-4...
    Como isso evoluiu com foco em estilização de navegador, é uma pena que não tenha recursos como seleção baseada no conteúdo de texto, como no XPath
    Pelo que sei, já houve propostas no passado, mas elas acabaram não entrando na spec por possíveis problemas de desempenho no contexto de renderização do navegador

    • LLMs também lidam bem com seletores CSS
      Ao criar um agente de edição de documentos, exibimos o documento em HTML e deixamos o LLM indicar apenas o CSS selector para puxar os trechos necessários para o contexto, e funcionou quase como mágica
    • No lado do cliente, querySelector/querySelectorAll já são tão amplamente usados que é ótimo ver isso chegando também ao novo DOM do PHP
      As pessoas podem usar exatamente o padrão com que já estão acostumadas
  • Seria bom existir um nome para separar a sintaxe do CSS de todo o sistema de regras, funções e unidades definido pelo CSSWG
    Há bastante potencial aí, mas para discutir ou pesquisar outros casos de uso, no fim parece que só resta vasculhar código no GitHub com parser de CSS embutido para ver que tipo de coisa estranha as pessoas estão criando
    Também estou mexendo com algo parecido com um mecanismo de templates estranho, misturando uma linguagem de marcação leve baseada em nós, seletores CSS para expressar o que entra no template, e uma sintaxe parecida com CSS para controlar como esses pedaços se combinam

    • Acho que os padrões já separam isso de forma bem clara
      https://www.w3.org/TR/selectors-3/
      A spec do DOM também faz referência a isso
      https://dom.spec.whatwg.org/#selectors
      Então o termo genérico CSS selector já está correto, e também dá para chamar só de selector
      O nome DOM selector talvez pareça mais limpo, mas se você considerar seletores usados em CSS estático ou em outros motores DOM fora de engines JavaScript, como parser XML, API DOM do PHP etc., isso pode acabar sendo ainda mais confuso
      Além disso, existem seletores especiais ligados diretamente à renderização e navegação do navegador, como :hover e ::target-text
      Ainda assim, pode ser útil ter um nome separado para o subconjunto mínimo da sintaxe de consulta menos acoplado ao navegador ou ao CSS
  • Isso me lembra https://github.com/braposo/graphql-css, que vi numa conferência há algum tempo
    Era um projeto de brincadeira, mas gostei porque mostrava bem como transplantar e reutilizar padrões em outros contextos pode tornar possíveis coisas inesperadas

    • Isso é divertido
      Estou justamente tentando reaproveitar padrões de contextos diferentes desse jeito
      Mesmo que a maior parte não vá muito longe, isso tem um apelo bem interessante para o espírito hacker
  • pyastgrep, como mostrado em https://pyastgrep.readthedocs.io/en/latest/, permite usar seletores CSS para consultar sintaxe Python
    O padrão é XPath e, por exemplo, dá para fazer pyastgrep --css 'Call > func > Name#main'

    • Isso é realmente ótimo
      Vai quase exatamente na direção que eu queria apontar
  • Não entendi bem que tipo de cenário isso resolve
    Hoje já dá para alterar condicionalmente um pai com base no filho. Por exemplo, pre tem padding padrão de 16px e, se o filho direto for code, dá para zerar isso com &:has(> code)

    • Na verdade, isso começou mais como a percepção de que duas ideias diferentes pareciam semelhantes, e acabou indo mais na linha de forçar essa conexão em várias direções
      A conclusão fica mais próxima de “precisamos corrigir os limites do CSS moderno” do que de imaginar se colocar uma sintaxe parecida com CSS sobre um sistema parecido com Datalog poderia tornar o trabalho com dados em árvore mais familiar para mais engenheiros
    • O que estou falando aqui não é algo resolvido com um único cálculo de estilo, mas uma sintaxe para modificar os próprios dados subjacentes dos alvos correspondidos por um seletor
      Ou seja, a ideia é adicionar novos elementos filhos ou atributos ao DOM
  • Os LLMs atuais não costumam lidar tão bem com CSS, então isso até dá vontade de testar para ver se, desse jeito, eles conseguiriam raciocinar de forma mais simples

  • Não consigo pensar em muita utilidade prática, mas ainda assim é legal

  • Hum... isso não é simplesmente JQ?

  • Até gosto de CSS até certo ponto, mas odeio como o complexity creep só piora
    Entendo a lógica de que linguagens de programação acabam ficando mais poderosas que linguagens não programáveis, mas em vez de continuar aumentando a complexidade de HTML, CSS e JavaScript, eu preferiria que surgisse outra coisa para substituir tudo isso
    Também quase nunca uso os novos elementos do HTML5, porque na maioria dos casos nem entendo por que eles são necessários. No fim, muitos containers parecem ser só div com IDs únicos e eu até gostaria que existisse algo como um alias para esses IDs para navegação href interna
    Coisas como [data-theme="dark"] [data-theme="light"] :focus { outline-color: black; } demoram demais para eu interpretar mentalmente e já não parecem mais elegantes nem simples
    Por outro lado, h2 { color: red; } ainda é simples
    Uma expressão como ancestor(X, Y) :- parent(X, Y). já me faz perder a vontade de pensar. Afinal, o que é :-? Parece uma carinha sorrindo
    Parei de ler em @container style(--theme: dark) { .card { background: royalblue; color: white; } }
    É estranho ver padrões que antes funcionavam bem parecerem se deteriorar com o tempo

    • Minha intenção não é adicionar mais sintaxe e semântica ao CSS, mas sim roubar ideias do CSS e usar a semelhança com linguagens de consulta lógica/relacional para criar algo novo
      Por exemplo, [data-theme="dark"] [data-theme="light"] :focus { outline-color: black; } em pseudocódigo mais próximo do inglês seria algo como: existe um X com data-theme="dark", seu filho Y tem data-theme="light" e está em foco, então outline-color de Y deve ser black
      Assim, em estilo Datalog, isso poderia ser escrito como outline-color(Y, black) if data-theme(X, "dark") and parent(X, Y) and data-theme(Y, "light") and focused(Y)
      Ou seja, trocando :- por if e vírgulas por and
      Indo além, também daria para escrever algo como Y.outline_color := black if X.data-theme == dark and Y.parent == X and Y.data-theme == dark and Y.focused, fazendo attr(X, val) parecer um açúcar sintático parecido com UFCS na forma X.attr == val
      Se quisesse algo com mais cara de família ALGOL, também poderia ser forall Y { Y.outline_color := black if Y.data_theme == "dark" and Y.focused and Y.parent.data_theme == "light" }
      Aqui Y é introduzido explicitamente e um dos joins fica implícito, então isso parece mais programação geral, mas na prática ainda seria um motor Datalog executando esse tipo de loop com eficiência sempre que as dependências mudassem