CSS를 질의 언어로 보기

7 hours ago 1
  • CSS 선택자와 선언은 이미 존재하는 요소 집합을 고르고 그 결과에 속성을 적용한다는 점에서, 관계를 질의하고 사실을 만드는 Datalog과 구조적으로 맞닿아 있음
  • 일반 CSS는 선택 결과를 다시 선택 조건으로 쓰는 재귀 계산을 지원하지 않아, dark 테마가 자손으로 전이되다가 light 경계에서 멈추는 식의 상태 전파를 직접 표현하지 못함
  • 가상의 CSSLog에서는 선택자 매칭에 영향을 주는 클래스 추가를 허용해 .effectively-dark 같은 파생 상태를 재귀적으로 전파하고, 새 결과가 더 이상 생기지 않을 때까지 반복 계산하게 됨
  • 이런 계산은 Datalog의 fixpoint와 monotonicity로 설명되며, 사실을 제거하지 않고 추가만 해야 반복 평가가 유한하게 끝날 수 있음
  • 실제 CSS의 container queries도 조상 상태를 읽을 수는 있지만 파생된 재귀 상태를 질의하지는 못해, CSS는 Datalog에 가까워지는 지점까지 가더라도 브라우저 렌더링 엔진의 경계를 넘지는 않음

CSS와 Datalog의 기본 대응

  • CSS는 이미 존재하는 대상을 전제로 하고, 여기서는 HTML 요소가 그 대상이 됨
    • h1, a, div 같은 요소는 CSS 바깥에 이미 존재하며, CSS는 그것들을 새로 선언하지 않음
    • 예시로 class, id, data-custom-attribute 같은 속성을 가진 요소가 제시됨
  • CSS 선택자는 공통 조건을 가진 집합을 가리키며, 태그명·id·class·속성값으로 대상을 좁힐 수 있음
    • div, #child, .awesome, [data-custom-attribute="foo"] 같은 선택자가 예시로 등장함
    • 문서 계층 내 위치 관계로도 대상을 표현할 수 있고, 선택자 결합으로 교집합도 만들 수 있음
  • div.awesome 같은 결합 선택자는 집합 교차를 수행하며, div이면서 동시에 .awesome인 요소만 선택함
    • 이 교차 개념은 뒤에서 Datalog의 join과 대응되는 핵심으로 이어짐
  • CSS 규칙은 선택자와 선언을 묶어 선택된 집합에 속성값을 적용함
    • div.awesome { color: red; font-size: 24px } 예시에서는 해당 요소들의 color와 font-size를 설정함
    • 브라우저에서는 그 결과로 큰 빨간 텍스트가 렌더링됨

CSS의 한계와 재귀 쿼리 문제

  • 일반 CSS는 언어 바깥 속성을 바꾸는 데는 강하지만, 그 변경 결과를 다시 선택 조건으로 직접 쓰지는 못함
    • 요소의 색상을 설정할 수는 있어도, div[color=red]처럼 색상 자체를 선택자 조건으로 삼는 예시는 브라우저가 거부함
    • color: red인 요소에 다시 color: blue를 적용하는 규칙은 의미가 불분명해짐
  • 디자인 시스템의 다크 모드 예시는 전이적 상태 전파가 필요한 문제로 제시됨
    • data-theme="dark"인 카드 내부의 모든 인터랙티브 요소에 흰색 포커스 아웃라인을 적용하고 싶음
    • 단, 중간에 data-theme="light"가 있으면 그 아래로는 전파가 멈춰야 함
  • 실제 CSS에서는 예외를 덧붙이는 방식으로 일부만 처리 가능함
    • [data-theme="dark"] :focus { outline-color: white; }로 기본 규칙을 만들고
    • [data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }로 light 경계를 되돌릴 수 있음
    • 하지만 이 방식은 중첩이 깊어질수록 규칙을 계속 추가해야 함
  • 이 문제는 재귀적 관계 정의가 필요하지만, CSS는 그것을 표현하지 못함
    • “스스로 dark이거나, effectively-dark 조상을 가지되 그 사이에 effectively-light 조상이 없는 경우 effectively-dark”라는 정의가 필요함
    • 원문은 이를 recursive relational definition으로 부르며, CSS는 이를 표현할 수 없다고 못박음

CSSLog 가상 문법

  • CSSLog는 일반 CSS처럼 선택자와 속성 설정을 유지하면서도, 선택자 매칭에 영향을 주는 속성까지 바꿀 수 있는 가상 버전으로 제시됨
    • 예시에서는 class: +bar처럼 클래스를 추가하는 문법이 등장함
    • 새 자식 요소를 만드는 +<div class="baz"> 같은 형태도 가정됨
    • 요소 삭제는 “아마도 안 될 것”으로만 적혀 있으며 추가 설명은 없음
  • div.foo 규칙이 실행된 뒤 같은 요소가 div.bar에도 매칭되는 식으로 규칙 실행 결과가 다음 매칭에 영향을 주게 됨
    • 이 지점에서는 한 번의 순방향 적용만으로 끝나지 않고 반복 계산이 필요해짐
  • 다크 모드 예시를 CSSLog로 옮기면 파생 클래스의 재귀 전파가 가능해짐
    • [data-theme="dark"] { class: +effectively-dark; }로 시작하고
    • .effectively-dark > :not([data-theme="light"]) { class: +effectively-dark; }로 자식에게 전파함
    • .effectively-dark :focus { outline-color: white; }로 최종 스타일을 적용함
  • 두 번째 규칙은 light 경계 전까지 재귀적으로 전파되며, 원하는 상태에 도달하면 멈추는 방식으로 묘사됨
    • 원문은 현재 CSS로는 이런 동작을 할 수 없다고 적고, 끝부분에서 일부 비슷한 우회도 다시 다룸

Datalog의 구조와 CSS와의 유사성

  • Datalog에서는 대상이 atoms로 불리며, 처음 언급되는 순간 존재하게 됨
    • alice, bob 같은 이름은 별도의 선언 없이 바로 사용됨
    • Ruby의 :symbols와 비교하는 문장이 함께 나옴
  • 집합과 관계는 relations와 tuples로 표현됨
    • parent(alice, bob)는 parent 관계 안의 튜플 하나가 됨
    • parent는 “첫 번째 대상이 두 번째 대상의 부모”라는 쌍들의 집합으로 설명됨
  • 변수는 질의 매칭과 집합 선택에 쓰임
    • parent(bob, X)는 Bob이 부모인 모든 X를 의미함
    • 이 예시에서는 X가 carol과 dave로 평가됨
    • 관례상 변수는 대문자, atom과 relation은 소문자를 사용함
  • 같은 변수명을 반복하면 join이 일어남
    • mother(X, Y) :- parent(X, Y), woman(X).는 부모 집합과 여성 집합의 교차로 어머니 관계를 만듦
    • 본문은 이를 “모든 부모”와 “모든 여성”의 교집합으로 설명함
  • Datalog 규칙의 :-는 if로 읽히며, 오른쪽 본문 조건이 모두 성립할 때 왼쪽 head 사실을 참으로 추가함
    • body의 쉼표는 and로 읽힘
    • ancestor(X, Y) :- parent(X, Y).는 X가 Y의 부모이면 조상이라는 뜻이 됨
  • CSS와 Datalog은 형태만 뒤집힌 유사 구조로 비교됨
    • color(X, red) :- div(X), class(X, awesome).는 “div이면서 awesome 클래스인 X의 색은 red”가 됨
    • CSS의 div.awesome { color: red; }와 의미 대응이 제시됨
    • 원문은 selector가 body이고, declaration이 head라고 정리함

재귀와 파생 사실

  • Datalog에서 무언가를 “한다”는 것은 새 사실을 파생하는 뜻이 됨
    • 기존 사실을 바탕으로 관계에 새 튜플을 추가하는 방식으로 동작함
  • ancestor 예시는 재귀 규칙의 전형으로 제시됨
    • ancestor(X, Y) :- parent(X, Y).는 직접 부모를 조상으로 만듦
    • ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).는 부모의 조상 관계를 따라 위로 확장함
  • 두 번째 규칙은 ancestor가 head와 body에 모두 등장하는 자기참조 재귀를 포함함
    • 이 규칙으로 alice -> bob -> carol, alice -> bob -> dave 같은 간접 조상 관계가 파생됨
  • 예시 실행 결과로 다음 다섯 사실이 제시됨
    • ancestor(alice, bob)
    • ancestor(bob, carol)
    • ancestor(bob, dave)
    • ancestor(alice, carol)
    • ancestor(alice, dave)
  • SQL도 WITH RECURSIVE 이전에는 이런 계산을 하지 못했고, 이 기능은 재귀 계산 수요 때문에 생겼다고 적음
    • 다만 원문은 SQL의 재귀 문법과 의미가 다른 부분들과 항상 잘 합성되지는 않는다고 덧붙임
  • Datalog은 for 루프 없이도 필요한 결과가 모두 나올 때까지 엔진이 알아서 계산을 계속함
    • 다음 섹션에서 그 이유를 fixpoint로 연결함

Fixpoint와 단조성

  • 일반 CSS의 cascade는 한 번의 순방향 적용으로 묘사됨
    • 브라우저가 규칙을 읽고, 선택자 매칭을 계산하고, 선언을 적용한 뒤 끝남
    • 피드백 루프는 없음
  • CSSLog와 실제 Datalog에서는 규칙 결과가 다른 규칙의 조건을 다시 만족시킬 수 있음
    • 어떤 규칙이 속성을 바꾸고, 그 속성 때문에 다른 규칙이 다시 실행되고, 다시 처음 규칙에도 영향을 주는 구조가 가능함
  • 순진한 Datalog 엔진은 새 사실이 더 이상 생기지 않을 때까지 규칙 적용을 반복함
    • 명시된 base fact부터 시작함
    • 모든 규칙의 body를 현재 사실 집합과 매칭함
    • 일치하는 경우 head 사실을 추가함
    • 새 사실이 생기면 다시 반복하고, 없으면 종료함
  • 이 종료 지점을 fixpoint라고 부름
    • 모든 규칙을 다시 적용해도 새 결과가 나오지 않는 상태가 됨
  • ancestor 예시는 세 라운드로 정리됨
    • 첫 라운드에서는 parent 사실들로부터 직접 조상 관계 세 개가 추가됨
    • 두 번째 라운드에서는 alice의 간접 조상 관계 두 개가 추가됨
    • 세 번째 라운드에서는 새 사실이 더 이상 생기지 않아 fixpoint에 도달함
  • 종료가 가능한 이유는 monotonicity에 있음
    • 사실을 제거하지 않고 오직 추가만 하므로, 알고 있는 사실 집합은 계속 커지기만 함
    • 유한한 시작 사실과 유한한 파생 가능성 아래에서는 유한한 작업 후에 멈출 수 있음
  • 반대로 사실 제거가 허용되면 나중 결과가 앞선 결과를 뒤집는 상황이 생겨 이 성질이 깨짐
    • 원문은 이를 Infinite Loop Land로 표현하며, 그래서 CSSLog가 요소 삭제를 허용하지 않는 쪽이 낫다고 연결함
  • 분산 시스템에서도 단조성은 비싼 조정 없이 일관성을 얻는 성질과 관련된다고 각주에서 덧붙임

왜 중요한가

  • CSS와 Datalog의 비교는 서로 다른 분야의 같은 구조를 드러냄
    • 둘 다 대상이 있고, 그 대상의 집합을 질의하며, 그 결과로 무언가를 적용하거나 새 사실을 만든다는 공통점이 있음
  • Datalog과 Prolog는 1970년대부터 관계형 데이터베이스와 당시의 AI 연구에서 등장했고, 이후 여러 형태로 다시 만들어져 왔음
  • “대상이 있고, 집합을 기술할 수 있으며, 그 대상에 작업을 할 수 있는” 시스템을 만들면 비슷한 지점으로 수렴한다는 관찰이 담겨 있음
  • 데이터베이스·로직 프로그래밍 분야와 프런트엔드 웹 개발 분야는 서로 자주 연결되지 않음
    • 서로 더 많이 연결되면 새로운 무언가를 함께 만들어낼 수 있다는 기대를 덧붙임

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는 파생된 상태를 읽지 못함
    • 조상의 커스텀 속성값은 조회할 수 있어도, 다른 규칙이 이미 계산해 둔 effectively-dark 같은 상태는 질의할 수 없음
    • DOM에 이미 있는 상태만 읽을 수 있고, 재귀 계산 결과는 보지 못함
  • 따라서 “어느 조상이라도 전이적으로 dark이고, 더 가까운 light 조상이 없으면 적용” 같은 질의는 재귀가 필요해 구현할 수 없음
    • 원문은 container queries에 재귀가 없다고 분명히 적음
  • 2015년 article은 element queries가 비슷한 이유로 계속 실패했던 배경을 다룸
    • 질의가 설정하는 속성을 다시 질의할 수 있게 되면 루프, 나아가 무한 루프가 생길 수 있음
  • CSS Working Group은 이런 문제를 정보 흐름 방향 제한으로 풀어 왔다고 정리됨
    • 자손은 조상 정보를 질의할 수 있지만 반대 방향은 허용하지 않음
    • 이렇게 하면 fixpoint 의미론 없이도 유한성을 유지할 수 있음
    • 정보는 트리 아래 방향으로만 전파되고, 새로운 base fact를 집어넣지 않는 구조로 유지됨
  • 원문은 CSS가 Datalog 엔진에 가까워지다가도 의도적으로 거기까지는 가지 않는 모습으로 이 흐름을 묘사함
    • CSSLog는 순환을 허용하고 fixpoint까지 평가하자는 쪽이고
    • 실제 CSS는 브라우저 렌더링 엔진이지 증분 관계형 데이터베이스 엔진은 아니라는 선에서 멈춤

다른 방향의 가능성

  • 브라우저에 Datalog 의미론을 넣는 대신, Datalog 위에 CSS 문법을 얹는 방향도 가능함
    • Datalog의 :-, 마침표, 대소문자 관례, 대입문 부재 같은 문법은 현대 언어 사용자에게 진입장벽으로 작용함
  • CSS는 이미 트리 구조를 직접 다루는 문법을 갖고 있음
    • 자손·자식·형제 결합자가 있어 부모-자식 관계를 자연스럽게 표현할 수 있음
    • 평범한 Datalog에서는 이런 구조를 관계형 형태로 다소 번거롭게 인코딩해야 함
  • 많은 실제 데이터가 트리 형태라는 점이 강조됨
    • JSON
    • AST
    • 파일시스템
    • 조직도
    • XML이 예시로 열거됨
  • fixpoint 재귀와 CSS풍 문법, 암묵적 부모-자식 관계를 결합한 도구가 있다면 재귀적 트리 질의를 더 익숙한 표기법으로 작성할 수 있게 됨
  • 원문 기준으로는 아직 그런 도구를 아무도 제대로 만들지 않은 듯 보임
    • 더 나은 이름의 “CSSLog” 같은 무언가를 누군가 만들어볼 만하다는 문장으로 마무리됨

각주

  • HTML 요소 단순화에 관한 각주가 붙어 있음
    • CSS가 다루는 대상이 꼭 전부 HTML 요소만은 아니라는 세부 반론을 예상하지만, 본문에서는 설명 단순화를 위해 HTML 요소로 다룸
  • naive evaluation은 이미 알고 있는 사실까지 매번 다시 계산하므로 비효율적임
    • 표준적인 개선 방식으로 semi-naive evaluation이 언급됨
    • 이는 매 단계에서 새로 파생된 사실만 본다는 점이 핵심임
  • 무한 루프 자체는 Turing-complete 언어에서 낯선 일이 아니라고 덧붙임
    • JavaScript에서도 while true {}를 쓸 수 있음
    • 다만 브라우저 렌더링 시스템이 웹사이트의 논리 혼동 때문에 영원히 멈추는 상황은 피하고 싶다는 맥락이 붙음
  • CSS의 custom property inheritance 우회도 각주에서 다뤄짐
    • [data-theme="dark"] { --effective-theme: dark; }
    • [data-theme="light"] { --effective-theme: light; }
    • @container style(--effective-theme: dark) { :focus { outline-color: white; } }
    • 이 방식은 이 특정 사례에서는 대체로 동작하지만, 상속은 실제 전이 폐쇄와 같지 않음
    • 부모-자식 이외의 속성 체인을 따라 전이 폐쇄가 필요한 더 복잡한 문제에서는 깨지게 됨
Read Entire Article