CSS를 질의 언어로 보기
(evdc.me)- 선택자와 규칙으로 대상 집합을 고르고 속성을 적용하는 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을 묶어, 선택된 요소에
color나font-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같은 규칙으로 일부만 처리할 수 있음 - 중첩 단계가 늘어나면 규칙을 계속 추가해야 하며, 재귀적 관계를 직접 표현하기 어려움
- 실제 CSS에서는
- 필요한 조건은 요소가 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 문법을 풍부하게 갖고 있음
- Datalog의
- 실제 데이터 중에는 트리 형태가 많다고 짚음
- 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에도 그게 들어온 게 반가움
사람들이 익숙한 방식 그대로 쓸 수 있음
- LLM도 CSS 셀렉터를 꽤 잘 다룸
-
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가 떠오름
농담 프로젝트였지만, 패턴을 다른 맥락으로 옮겨 심고 다시 쓰는 방식이 의외의 걸 가능하게 한다는 점을 잘 보여줘서 좋았음- 이거 재밌네
바로 그런 식으로 서로 다른 맥락의 패턴을 가져다 써보려는 중임
대부분은 별 데까지 안 가더라도, 해커 감성으로는 꽤 흥미로움
- 이거 재밌네
-
pyastgrep은 https://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 엔진이 의존성이 바뀔 때마다 이런 루프를 효율적으로 돌려주는 셈임
- 내 의도는 CSS에 문법과 의미를 더 얹자는 쪽이 아니라, CSS에서 아이디어를 훔쳐와서 논리/관계형 질의 언어와의 유사성을 활용해 새로운 무언가를 만들자는 쪽에 더 가까움