1P by GN⁺ 8시간전 | ★ favorite | 댓글 1개
  • 선택자와 규칙으로 대상 집합을 고르고 속성을 적용하는 CSS의 구조는, 집합과 규칙으로 동작하는 Datalog와 형태적으로 닮아 있음
  • div.awesome 같은 선택자 결합은 교집합을 만들고, Datalog에서는 같은 변수를 반복하는 방식으로 비슷한 join이 일어남
  • 현재 CSS는 계산된 스타일 결과를 다시 선택 조건으로 삼지 못해, 재귀적 전이 질의나 파생 상태의 반복 전파를 직접 표현하기 어려움
  • Datalog은 재귀 규칙고정점 평가로 새 사실이 더 이상 생기지 않을 때까지 관계를 확장하며, 단조성 덕분에 유한한 범위에서 계산을 끝낼 수 있음
  • 실제 CSS는 Container Queries 같은 기능으로 조상 정보를 읽을 수 있지만 피드백 루프와 순환을 막는 방향을 택했고, 대신 CSS 문법을 재귀 질의에 접목할 여지는 남아 있음

CSS와 Datalog의 닮은 구조

  • CSS는 대상 집합 선택선택된 대상에 대한 규칙 적용이라는 구조를 가짐
    • HTML 요소 같은 "Things"가 먼저 존재하고, selector가 공통 속성을 가진 집합을 가리킴
    • div, #child, .awesome, [data-custom-attribute="foo"] 같은 selector로 집합을 기술할 수 있음
    • div.awesome처럼 selector를 결합해 교집합을 만들 수 있음
  • CSS 규칙은 selector와 declaration을 묶어, 선택된 요소에 colorfont-size 같은 속성을 설정함
    • 다만 이런 속성은 대체로 언어 바깥의 상태를 바꾸며, 그 결과를 다시 selector 조건으로 삼지는 못함
    • div[color=red]처럼 스타일 결과를 다시 질의하는 형태는 브라우저가 받아들이지 않음
  • Datalog도 비슷하게 사실 집합규칙 기반 도출로 동작함
    • parent(alice, bob) 같은 원자와 relation이 기본 단위가 됨
    • 변수 X, Y를 써서 조건에 맞는 항목 집합을 고를 수 있음
    • 같은 변수를 반복해 조건을 연결하면 CSS의 selector 결합과 비슷한 join이 일어남
  • head(X, Y) :- body1(X, Z), body2(Z, Y) 구조는 CSS 규칙과 방향만 반대일 뿐 형태가 유사함
    • CSS의 selector가 Datalog의 body에 가깝고, declaration은 head에 가까움
    • div.awesome { color: red; }color(X, red) :- div(X), class(X, awesome).와 대응됨

CSS가 못 하는 재귀적 질의

  • data-theme="dark" 안의 모든 포커스 요소에 반전 스타일을 주되, 중간에 data-theme="light"가 나오면 멈추는 조건은 전이적 질의를 필요로 함
    • 실제 CSS에서는 [data-theme="dark"] :focus[data-theme="dark"] [data-theme="light"] :focus 같은 규칙으로 일부만 처리할 수 있음
    • 중첩 단계가 늘어나면 규칙을 계속 추가해야 하며, 재귀적 관계를 직접 표현하기 어려움
  • 필요한 조건은 요소가 effectively-dark인지 재귀적으로 판정하는 일임
    • 자신이 data-theme="dark"이면 effectively-dark가 됨
    • effectively-dark 조상 아래의 자식도, 중간에 data-theme="light"가 없으면 effectively-dark가 됨
    • 이 상태를 바탕으로 .effectively-dark :focus에 스타일을 적용해야 함
  • 가상의 CSSLog 문법에서는 규칙이 class: +effectively-dark처럼 파생 상태를 추가할 수 있음
    • .effectively-dark > :not([data-theme="light"])가 자식으로 상태를 전파함
    • 규칙은 목표 상태에 도달할 때까지 재귀적으로 반복돼야 함
  • 이런 종류의 재귀 전파는 현재 CSS로는 표현하기 어려움
    • 글 말미에서 일부 비슷하게 흉내 내는 방법도 나오지만, 같은 원리의 일반 해법은 아님

Datalog의 재귀와 고정점

  • Datalog은 기존 사실에서 새 사실을 도출하는 방식으로 동작하며, 재귀를 기본적으로 다룸
    • ancestor(X, Y) :- parent(X, Y).
    • ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
  • ancestor 규칙은 부모 관계를 바탕으로 조상 관계를 단계적으로 확장함
    • parent(alice, bob)에서 ancestor(alice, bob)가 먼저 생김
    • 이어서 alice -> bob -> carol, alice -> bob -> dave 같은 경로도 추가로 도출됨
  • 이 계산은 명시적 for 루프 없이도 고정점 평가로 끝까지 진행됨
    • 처음에는 명시된 base fact만 사용함
    • 모든 규칙의 body를 현재 사실 집합에 대입해 head를 추가함
    • 새 사실이 더 이상 생기지 않을 때 멈춤
  • 이 방식이 끝나는 이유는 단조성에 있음
    • 사실을 추가만 하고 제거하지 않으므로, 알려진 사실 집합은 계속 커지기만 함
    • 유한한 사실 집합에서 시작하면 유도 가능한 사실 수도 유한하게 제한됨
    • 반대로 사실을 제거할 수 있으면 이전 결론이 뒤집히며 무한 루프에 빠질 수 있음

Container Queries와 실제 CSS의 경계

  • 실제 CSS의 Container Queries는 조상이나 컨테이너의 스타일을 기준으로 규칙을 적용할 수 있음
    • @container style(--theme: dark) { .card { background: royalblue; color: white; } } 같은 형태를 지원함
  • 하지만 transitive dark mode 예시는 단순한 조상 조회보다 더 강한 조건을 요구함
    • 각 요소가 자기 자신이 effectively-dark인지 알아야 함
    • 그 상태가 자손 전체로 전이적으로 전파돼야 함
    • data-theme="light" 경계에서 전파가 멈춰야 함
  • Container Queries는 2번째 조건을 처리하지 못함
    • 조상의 custom property는 읽을 수 있어도, 다른 규칙이 이미 계산한 파생 상태를 다시 질의하지는 못함
    • DOM에 원래 있던 정보는 볼 수 있지만, 재귀 계산 결과를 selector 조건으로 삼지는 못함
  • 2015년의 관련 글도 element queries가 같은 문제에 부딪혔다고 짚음
    • 질의로 설정한 속성을 다시 질의할 수 있게 되면 루프와 무한 반복 위험이 커짐
  • CSS Working Group은 이 문제를 정보 흐름 방향 제한으로 피해 왔음
    • 자손이 조상 정보를 질의하는 것은 허용함
    • 반대 방향 피드백이나 자기 자신 스타일로의 순환은 막음
    • 그래서 고정점 의미론 없이도 계산을 유한하게 유지함

CSS 문법을 재귀 질의 언어로 뒤집는 가능성

  • Datalog 의미론을 CSS에 넣기보다, CSS 문법을 Datalog 위에 얹는 방향이 더 현실적인 새 길로 제시됨
    • Datalog의 :-, 마침표, 선언 없는 원자 같은 문법은 현대 언어 사용자에게 진입 장벽이 큼
    • CSS는 이미 트리 구조를 다루는 selector 문법을 풍부하게 갖고 있음
  • 실제 데이터 중에는 트리 형태가 많다고 짚음
    • JSON
    • AST
    • 파일시스템
    • 조직도
    • XML
  • 이런 영역에서는 부모/자식 관계를 암묵적으로 다루는 CSS풍 문법과 고정점 재귀를 결합하면 유용할 수 있음
    • 일반 Datalog은 트리 구조를 관계형 표현으로 옮겨 적어야 해서 번거로움이 큼
    • CSS selector 감각을 재귀 질의에 그대로 가져오면 더 많은 프로그래머가 쉽게 접근할 수 있음
  • 이런 형태의 도구는 아직 뚜렷하게 보이지 않음
    • "CSSLog"라는 이름은 임시적이며, 더 나은 이름의 언어가 나올 수 있음
    • 재귀적 트리 질의를 더 익숙한 표기법으로 다룰 여지는 남아 있음

보충 논점과 참고 링크

  • Datalog은 1970년대부터 관계형 데이터베이스와 당시의 AI 연구 맥락에서 등장했으며, 이후 여러 형태로 반복해서 재등장함
  • 고정점 계산의 단순한 형태는 naive evaluation으로 소개되지만, 이미 알려진 사실을 매번 다시 계산해 비효율적일 수 있음
    • 각 단계에서 새로 나온 사실만 활용하는 semi-naive evaluation이 대표적 개선 방향으로 함께 언급됨
  • 단조성은 분산 시스템에서도 유익한 성질로 이어짐
  • custom property 상속으로 transitive dark mode를 부분적으로 흉내 내는 방법도 있음
    • [data-theme="dark"] { --effective-theme: dark; }
    • [data-theme="light"] { --effective-theme: light; }
    • @container style(--effective-theme: dark) { :focus { outline-color: white; } }
    • 이 방법은 이 특정 경우에는 대체로 동작하지만, 진짜 전이 폐쇄를 일반적으로 제공하지는 못함
Hacker News 의견들
  • CSS 셀렉터XPath보다 훨씬 쓰기 쉬움
    최근 PHP의 새 DOM API가 HTML과 CSS 셀렉터를 네이티브하게 아주 쉽게 다루게 해준다는 내용으로 발표도 했음. 예전에는 CSS를 XPath로 변환해야 했음
    [1] https://speakerdeck.com/keyvan/parsing-html-with-php-8-dot-4...
    브라우저 스타일링 중심으로 발전해 와서, XPath처럼 텍스트 내용 기반 선택 같은 기능이 없는 건 아쉬움
    예전에 제안은 있었지만 브라우저 렌더링 맥락에서 성능 문제가 생길 수 있어 스펙에 못 들어간 걸로 알고 있음

    • LLM도 CSS 셀렉터를 꽤 잘 다룸
      문서 편집 에이전트를 만들면서 문서를 HTML로 보여주고, LLM이 CSS selector만 지정해서 필요한 조각을 컨텍스트로 끌어오게 했는데 거의 마법처럼 잘 됨
    • 클라이언트 쪽에서는 querySelector/querySelectorAll이 워낙 널리 쓰이니, 이제 PHP의 새 DOM에도 그게 들어온 게 반가움
      사람들이 익숙한 방식 그대로 쓸 수 있음
  • CSS 문법과 CSSWG가 정의하는 규칙·함수·단위 같은 전체 체계를 분리해서 부를 이름이 있으면 좋겠음
    이쪽에 꽤 가능성이 있는데, 다른 활용 사례를 이야기하거나 조사하려면 결국 CSS 파서가 들어간 GitHub 코드를 뒤져보며 사람들이 어떤 기묘한 걸 만드는지 확인하는 수밖에 없어 보임
    가벼운 노드 기반 마크업 언어와, 템플릿에 무엇이 들어갈지 표현하는 CSS 셀렉터, 그리고 이 조각들을 어떻게 결합할지 제어하는 CSS 비슷한 문법을 섞은 이상한 템플릿 엔진 비슷한 것도 만지작거리고 있음

    • 표준에서는 이미 꽤 명확히 분리돼 있다고 봄
      https://www.w3.org/TR/selectors-3/
      DOM 스펙도 이걸 참조함
      https://dom.spec.whatwg.org/#selectors
      그래서 CSS selector라는 통칭은 이미 맞고, 그냥 selector라고 불러도 됨
      DOM selector라는 이름이 더 깔끔할 수도 있지만, 정적 CSS에서 쓰는 셀렉터나 JS 엔진 바깥의 다른 DOM 엔진(XML parser, PHP DOM API 등)에서 쓰는 셀렉터까지 생각하면 오히려 더 헷갈릴 수도 있음
      :hover::target-text처럼 브라우저 렌더링·탐색에 직접 묶인 특수 셀렉터도 있음
      다만 브라우저나 CSS와 덜 결합된 최소 질의 문법 부분집합에는 별도 이름이 있으면 유용할 수 있음
  • 예전에 컨퍼런스에서 봤던 https://github.com/braposo/graphql-css가 떠오름
    농담 프로젝트였지만, 패턴을 다른 맥락으로 옮겨 심고 다시 쓰는 방식이 의외의 걸 가능하게 한다는 점을 잘 보여줘서 좋았음

    • 이거 재밌네
      바로 그런 식으로 서로 다른 맥락의 패턴을 가져다 써보려는 중임
      대부분은 별 데까지 안 가더라도, 해커 감성으로는 꽤 흥미로움
  • pyastgrephttps://pyastgrep.readthedocs.io/en/latest/에서 보듯이 Python 문법을 질의할 때 CSS 셀렉터를 쓸 수 있음
    기본값은 XPath고, 예를 들면 pyastgrep --css 'Call > func > Name#main'처럼 가능함

    • 이거 정말 좋네
      내가 가리키고 싶었던 방향과 거의 정확히 맞닿아 있음
  • 이게 어떤 시나리오를 해결하는지 잘 모르겠음
    지금도 자식에 따라 부모를 조건부로 바꿀 수 있음. 예를 들어 pre는 기본 패딩이 16px이고, 직접 자식이 code&:has(> code)로 0으로 만들 수 있음

    • 사실 이건 처음엔 서로 다른 두 아이디어가 닮아 보인다는 데서 출발했고, 그 연결을 여러 방향으로 밀어본 쪽에 가까움
      결론도 "현대 CSS의 한계를 고쳐야 한다"보다는, CSS 비슷한 문법Datalog 비슷한 시스템에 얹으면 트리 형태 데이터를 다루는 일을 더 많은 엔지니어에게 친숙하게 만들 수 있지 않을까에 가까움
    • 여기서 말하는 건 한 번의 스타일 계산으로 해결하는 게 아니라, 선택자에 매칭된 대상의 기저 데이터 자체를 수정하는 문법임
      즉 DOM에 새 자식 요소나 속성을 추가하는 쪽 이야기임
  • 지금의 LLM은 CSS를 잘 못 다루는 편이라, 오히려 이걸 시도해 보면 LLM이 더 단순하게 추론할 수 있는지 확인해보고 싶어짐

  • 실제 쓸모는 잘 떠오르지 않지만 그래도 멋지긴 함

  • 음... 이거 그냥 JQ 아닌가 싶음

  • CSS는 어느 정도 좋아하지만, 점점 복잡성 creep이 심해지는 건 싫음
    프로그래밍 언어가 비프로그래밍 언어보다 더 강력해진다는 논리는 이해하지만, HTML·CSS·JavaScript를 계속 복잡하게 키우느니 차라리 그 전체를 대체하는 다른 무언가가 나오는 편이 낫다고 느낌
    HTML5의 새 요소들도 대부분 왜 필요한지 잘 모르겠어서 거의 안 씀. 결국 많은 컨테이너는 고유 ID가 붙은 div일 뿐이라고 생각하게 됐고, 심지어 내부 링크용 href 탐색을 위해 그런 ID에 별칭 같은 게 있었으면 싶었음
    [data-theme="dark"] [data-theme="light"] :focus { outline-color: black; } 같은 것도 머리로 해석하는 데 너무 오래 걸려서 더 이상 우아하고 단순하지 않게 느껴짐
    반면 h2 { color: red; }는 여전히 단순함
    ancestor(X, Y) :- parent(X, Y). 같은 표현도 벌써 생각하기 싫어짐. :-는 대체 뭐고, 웃는 얼굴처럼 보임
    @container style(--theme: dark) { .card { background: royalblue; color: white; } }에서 읽기를 멈췄음
    예전엔 잘 작동하던 표준이 시간이 갈수록 망가지는 것 같아 이상함

    • 내 의도는 CSS에 문법과 의미를 더 얹자는 쪽이 아니라, CSS에서 아이디어를 훔쳐와서 논리/관계형 질의 언어와의 유사성을 활용해 새로운 무언가를 만들자는 쪽에 더 가까움
      예를 들어 [data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }는 영어식 의사코드로 풀면, data-theme="dark"인 X가 있고 그 자식 Y가 data-theme="light"이며 포커스 상태면 Y의 outline-color를 black으로 하라는 뜻에 가까움
      그래서 이건 Datalog 식으로 outline-color(Y, black) if data-theme(X, "dark") and parent(X, Y) and data-theme(Y, "light") and focused(Y)처럼 쓸 수 있음
      :-if로, 쉼표를 and로 바꾼 셈임
      더 나아가 Y.outline_color := black if X.data-theme == dark and Y.parent == X and Y.data-theme == dark and Y.focused처럼 적어서, attr(X, val)X.attr == val 같은 UFCS 비슷한 문법 설탕으로 보이게 만들 수도 있음
      더 ALGOL 계열처럼 보이게 하려면 forall Y { Y.outline_color := black if Y.data_theme == "dark" and Y.focused and Y.parent.data_theme == "light" }처럼도 가능함
      여기서는 Y를 명시적으로 도입하고 조인 하나는 암묵화해서 더 일반적인 프로그래밍처럼 보이지만, 실제로는 Datalog 엔진이 의존성이 바뀔 때마다 이런 루프를 효율적으로 돌려주는 셈임