CSS như một ngôn ngữ truy vấn: Khi phong cách gặp gỡ logic
Bài viết khám phá ý tưởng sử dụng CSS không chỉ để định dạng trang web mà còn như một ngôn ngữ truy vấn, tương tự như Datalog trong lập trình logic. Tác giả đề xuất khái niệm "CSSLog" để giải quyết các vấn đề phức tạp như chế độ tối chuyển tiếp, phân tích sự tương đồng về cấu trúc giữa bộ chọn CSS và các quy tắc logic, cũng như tiềm năng của việc kết hợp cú pháp CSS với ngữ nghĩa đệ quy.
Bạn có bao giờ tự hỏi liệu CSS – ngôn ngữ thường dùng để tô màu và căn chỉnh văn bản – có thể được sử dụng như một ngôn ngữ truy vấn thậm chí là một ngôn ngữ lập trình mục đích chung không? Mặc dù nghe có vẻ điên rồ khi so sánh CSS với các ngôn ngữ truy vấn mạnh mẽ như SQL hay Datalog, nhưng có những điểm tương đồng thú vị giữa cách CSS hoạt động và logic học.
Các nguyên lý cơ bản của CSS
Để hiểu rõ hơn, hãy nhìn nhận CSS dưới góc độ của một ngôn ngữ truy vấn:
- Có các "Thực thể" (Things): Trong CSS, các thực thể này là các phần tử HTML. Chúng tồn tại độc lập với CSS.
- Mô tả tập hợp các thực thể: Chúng ta sử dụng các bộ chọn (selectors) để tham chiếu đến các tập hợp phần tử có chung đặc điểm. Ví dụ,
divchọn tất cả các thẻ div,.awesomechọn các phần tử có class "awesome". Chúng ta cũng có thể kết hợp các bộ chọn để thực hiện phép giao tập hợp. - Thực hiện hành động: Chúng ta định nghĩa các quy tắc kết hợp bộ chọn với các khai báo (declarations) để thay đổi thuộc tính của các phần tử đó.
Về cơ bản, bạn đang nói: "Đối với tất cả các phần tử là div và có class 'awesome', hãy đặt màu của chúng thành đỏ."
Hạn chế của CSS hiện tại
Tuy nhiên, CSS có một hạn chế lớn. Các khai báo thường thay đổi các thuộc tính nằm ngoài ngôn ngữ (như màu sắc của phần tử), nhưng bạn không thể chọn một phần tử dựa trên thuộc tính mà chính CSS vừa đặt cho nó.
Ví dụ, bạn không thể viết div[color=red] { color: blue; }. Trình duyệt sẽ từ chối điều này vì nó tạo ra một nghịch lý về logic và trạng thái.
Hãy xem xét một tình huống thực tế: Bạn đang xây dựng một hệ thống thiết kế có hỗ trợ "dark mode". Bạn muốn mọi phần tử tương tác bên trong một thẻ data-theme="dark" đều có kiểu focus đảo ngược, bất kể nó lồng nhau sâu đến đâu, trừ khi có một thành phần trung gian đã tắt chế độ này bằng data-theme="light".
Với CSS thông thường, bạn sẽ phải viết rất nhiều quy tắc để xử lý từng cấp độ lồng nhau. Điều này giống như việc bạn đang viết một phiên bản truy vấn chuyển tiếp (transitive query) một cách thủ công và thiếu chính xác. Những gì bạn thực sự muốn là một định nghĩa quan hệ đệ quy, mà CSS hiện tại không thể diễn đạt.
Sự trỗi dậy của Datalog
Đây là lúc Datalog – một ngôn ngữ lập trình logic dựa trên cơ sở dữ liệu quan hệ – xuất hiện trong bức tranh. Datalog có cú pháp trông rất lạ lẫm với những người dùng ngôn ngữ hiện đại:
parent(alice, bob).
ancestor(X, Y) :- parent(X, Y).
ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
Mặc dù trông khác biệt, Datalog và CSS có cấu trúc rất giống nhau:
- Cả hai đều có "Thực thể" (HTML elements hoặc atoms).
- Cả hai đều có thể "Mô tả tập hợp các thực thể" thông qua các truy vấn liên kết (selectors hoặc rule bodies).
- Cả hai đều có thể "Làm gì đó với các thực thể" đó.
Sự khác biệt chính là chiều hướng. Trong Datalog, phần thân (body) là điều kiện và phần đầu (head) là fact mới được suy ra. Trong CSS, bộ chọn (selector) là điều kiện và các khai báo là hành động. Bạn có thể coi :- trong Datalog giống như { trong CSS, nhưng ngược chiều.
Điểm cố định (Fixpoint) và Đệ quy
Điểm mạnh nhất của Datalog là khả năng đệ quy và khái niệm "điểm cố định" (fixpoint).
Trong CSS thông thường, cơ chế "cascade" chỉ là một lần chuyển tiếp: trình duyệt đọc quy tắc, khớp bộ chọn và áp dụng khai báo. Không có vòng phản hồi.
Trong Datalog, một quy tắc có thể thiết lập một thuộc tính khiến quy tắc khác kích hoạt, quy tắc đó lại quay ngược lại kích hoạt quy tắc đầu tiên. Bạn không thể chỉ chạy một lần. Bạn phải tiếp tục chạy cho đến khi không còn fact mới nào được thêm vào. Đó là điểm cố định.
Ví dụ, để tìm ra tổ tiên (ancestor), Datalog sẽ chạy:
- Nếu A là cha của B, thì A là tổ tiên của B.
- Nếu A là cha của B, và B là tổ tiên của C, thì A cũng là tổ tiên của C.
Quá trình này lặp lại cho đến khi tất cả các mối quan hệ tổ tiên được tìm ra. CSS không thể làm điều này một cách tự nhiên.
CSSLog: Một hướng đi mới?
Giả sử chúng ta tạo ra một phiên bản CSS gọi là "CSSLog", nơi các bộ chọn có thể đặt các thuộc tính ảnh hưởng đến việc các bộ chọn khác có khớp hay không, và cho phép đệ quy đến điểm cố định.
Với CSSLog, vấn đề "dark mode" chuyển tiếp có thể được giải quyết dễ dàng:
- Nếu phần tử có
data-theme="dark", thêm classeffectively-dark. - Nếu phần tử cha có class
effectively-darkvà phần tử con không códata-theme="light", thì phần tử con cũng nhận classeffectively-dark. - Áp dụng kiểu cho tất cả phần tử
effectively-dark.
Quy tắc thứ hai sẽ lan truyền trạng thái "dark" xuống cây DOM một cách đệ quy cho đến khi gặp ranh giới "light".
Thực tế và Tiềm năng
Nhóm làm việc CSS (CSS Working Group) đã đi gần đến khái niệm này với tính năng Container Queries. Nó cho phép truy vấn kiểu của phần tử cha, nhưng bị hạn chế bởi việc không có đệ quy và không thể đọc trạng thái đã được suy ra (derived state). Họ đã tránh việc tạo ra một engine Datalog thực sự trong trình duyệt để ngăn chặn các vòng lặp vô hạn có thể làm treo trình duyệt.
Tuy nhiên, ý tưởng đặt cú pháp CSS lên trên ngữ nghĩa của Datalog lại rất hấp dẫn. Cú pháp CSS thân thiện với lập trình viên hơn nhiều so với cú pháp logic học khô khan của Datalog. Hơn nữa, CSS đã có khái niệm sẵn về cấu trúc cây (cha/con, anh em).
Rất nhiều dữ liệu trong thế giới thực có cấu trúc dạng cây: JSON, AST (cây cú pháp trừu tượng), hệ thống tệp, sơ đồ tổ chức. Một công cụ "CSSLog" thực sự, với cú pháp kiểu CSS và khả năng đệ quy fixpoint, có thể là một công cụ tuyệt vời để truy vấn và biến đổi các dữ liệu dạng cây này.
Có lẽ đã đến lúc cộng đồng lập trình web và cộng đồng cơ sở dữ liệu logic ngồi lại với nhau để xem họ có thể tạo ra gì từ sự kết hợp thú vị này.


