1 điểm bởi GN⁺ 14 ngày trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Cấu trúc của CSS, với selector và rule để chọn một tập đối tượng rồi áp dụng thuộc tính, về mặt hình thức khá giống Datalog, vốn cũng hoạt động bằng tập hợp và quy tắc
  • Việc kết hợp selector như div.awesome tạo ra giao của các tập, còn trong Datalog thì hiệu ứng join tương tự xuất hiện bằng cách lặp lại cùng một biến
  • CSS hiện nay không thể dùng kết quả style đã tính toán làm điều kiện chọn tiếp theo, nên khó biểu đạt trực tiếp truy vấn bắc cầu đệ quy hay việc lan truyền lặp lại của trạng thái suy diễn
  • Datalog mở rộng quan hệ bằng quy tắc đệ quyđánh giá điểm cố định, cho đến khi không còn sự kiện mới nào được tạo ra; nhờ tính đơn điệu nên có thể kết thúc tính toán trong phạm vi hữu hạn
  • CSS thực tế có thể đọc thông tin tổ tiên bằng các tính năng như Container Queries, nhưng đã chọn hướng ngăn vòng phản hồi và chu trình; dù vậy vẫn còn dư địa để kết hợp cú pháp CSS với truy vấn đệ quy

Cấu trúc tương đồng giữa CSS và Datalog

  • CSS có cấu trúc gồm chọn tập đối tượng mục tiêuáp dụng quy tắc lên các đối tượng đã chọn
    • Trước tiên tồn tại các "Things" như phần tử HTML, rồi selector chỉ tới tập hợp có chung thuộc tính
    • Có thể mô tả tập hợp bằng các selector như div, #child, .awesome, [data-custom-attribute="foo"]
    • Có thể kết hợp selector như div.awesome để tạo giao tập
  • Quy tắc CSS ghép selector và declaration để thiết lập các thuộc tính như color hay font-size cho phần tử được chọn
    • Tuy nhiên các thuộc tính này phần lớn thay đổi trạng thái bên ngoài ngôn ngữ, và không thể lấy kết quả đó làm điều kiện selector tiếp theo
    • Dạng truy vấn lại kết quả style như div[color=red] không được trình duyệt chấp nhận
  • Datalog cũng vận hành tương tự với tập sự kiệnsuy diễn dựa trên quy tắc
    • Các atom và relation như parent(alice, bob) là đơn vị cơ bản
    • Có thể dùng biến X, Y để chọn tập phần tử thỏa điều kiện
    • Khi lặp lại cùng một biến để nối điều kiện, sẽ xảy ra join tương tự việc kết hợp selector trong CSS
  • Cấu trúc head(X, Y) :- body1(X, Z), body2(Z, Y) chỉ ngược chiều với quy tắc CSS, còn hình thức thì tương tự
    • Selector của CSS gần với body của Datalog hơn, còn declaration gần với head hơn
    • div.awesome { color: red; } tương ứng với color(X, red) :- div(X), class(X, awesome).

Truy vấn đệ quy mà CSS không làm được

  • Điều kiện áp dụng style đảo màu cho mọi phần tử đang focus bên trong data-theme="dark", nhưng dừng lại nếu gặp data-theme="light" ở giữa, đòi hỏi một truy vấn bắc cầu
    • Trong CSS thực tế, chỉ có thể xử lý một phần bằng các rule như [data-theme="dark"] :focus[data-theme="dark"] [data-theme="light"] :focus
    • Khi số mức lồng nhau tăng lên, phải tiếp tục thêm rule, và rất khó biểu đạt trực tiếp quan hệ đệ quy
  • Điều kiện cần thiết là phải xác định một cách đệ quy liệu phần tử có effectively-dark hay không
    • Nếu bản thân nó là data-theme="dark" thì trở thành effectively-dark
    • Con của một tổ tiên effectively-dark cũng là effectively-dark, miễn là ở giữa không có data-theme="light"
    • Dựa trên trạng thái này mới có thể áp dụng style cho .effectively-dark :focus
  • Trong cú pháp CSSLog giả định, rule có thể thêm trạng thái suy diễn như class: +effectively-dark
    • .effectively-dark > :not([data-theme="light"]) sẽ lan truyền trạng thái xuống phần tử con
    • Rule phải được lặp đệ quy cho đến khi đạt trạng thái mục tiêu
  • Kiểu lan truyền đệ quy này hiện rất khó biểu đạt bằng CSS hiện nay
    • Cuối bài cũng nêu một số cách mô phỏng gần giống, nhưng đó không phải là lời giải tổng quát theo cùng nguyên lý

Đệ quy và điểm cố định trong Datalog

  • Datalog hoạt động bằng cách suy ra sự kiện mới từ các sự kiện hiện có, và về bản chất xử lý đệ quy
    • ancestor(X, Y) :- parent(X, Y).
    • ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
  • Quy tắc ancestor mở rộng quan hệ tổ tiên từng bước dựa trên quan hệ cha mẹ
    • Từ parent(alice, bob) trước hết sinh ra ancestor(alice, bob)
    • Sau đó tiếp tục suy ra các đường đi như alice -> bob -> carol, alice -> bob -> dave
  • Quá trình tính toán này có thể đi đến tận cùng bằng đánh giá điểm cố định mà không cần vòng lặp for tường minh
    • Ban đầu chỉ dùng các base fact đã khai báo
    • Áp body của mọi rule lên tập sự kiện hiện tại rồi thêm head
    • Dừng lại khi không còn sự kiện mới nào xuất hiện
  • Lý do cách này kết thúc nằm ở tính đơn điệu
    • Chỉ thêm sự kiện chứ không xóa, nên tập sự kiện đã biết chỉ tiếp tục lớn dần
    • Nếu bắt đầu từ tập sự kiện hữu hạn thì số sự kiện có thể suy ra cũng bị giới hạn hữu hạn
    • Ngược lại, nếu có thể xóa sự kiện thì kết luận trước đó có thể bị đảo ngược và rơi vào vòng lặp vô hạn

Container Queries và ranh giới của CSS thực tế

  • Container Queries trong CSS thực tế cho phép áp dụng rule dựa trên style của tổ tiên hoặc container
    • Hỗ trợ các dạng như @container style(--theme: dark) { .card { background: royalblue; color: white; } }
  • Tuy nhiên ví dụ dark mode bắc cầu đòi hỏi điều kiện mạnh hơn việc chỉ tra cứu tổ tiên đơn thuần
    • Mỗi phần tử phải biết liệu chính nó có effectively-dark hay không
    • Trạng thái đó phải được lan truyền bắc cầu tới toàn bộ hậu duệ
    • Việc lan truyền phải dừng ở ranh giới data-theme="light"
  • Container Queries không xử lý được điều kiện thứ hai
    • Dù có thể đọc custom property của tổ tiên, nó không thể truy vấn lại trạng thái suy diễn mà rule khác đã tính ra
    • Nó nhìn thấy thông tin vốn có trong DOM, nhưng không thể dùng kết quả tính toán đệ quy làm điều kiện selector
  • Bài viết liên quan từ năm 2015 cũng chỉ ra rằng element queries gặp đúng vấn đề này
    • Nếu cho phép truy vấn lại thuộc tính đã được thiết lập bởi truy vấn, nguy cơ vòng lặp và lặp vô hạn sẽ tăng cao
  • CSS Working Group từ trước đến nay đã tránh vấn đề này bằng cách giới hạn hướng luồng thông tin
    • Cho phép hậu duệ truy vấn thông tin của tổ tiên
    • Nhưng chặn phản hồi theo chiều ngược lại hoặc chu trình quay về style của chính phần tử đó
    • Nhờ vậy vẫn giữ được tính toán hữu hạn mà không cần ngữ nghĩa điểm cố định

Khả năng đảo ngược cú pháp CSS thành ngôn ngữ truy vấn đệ quy

  • Thay vì nhét ngữ nghĩa Datalog vào CSS, bài viết cho rằng một hướng thực tế hơn là đặt cú pháp CSS lên trên Datalog
    • Cú pháp như :-, dấu chấm và atom không khai báo của Datalog là rào cản tiếp cận lớn với người dùng ngôn ngữ hiện đại
    • Trong khi đó CSS vốn đã có hệ cú pháp selector rất phong phú để xử lý cấu trúc cây
  • Bài viết chỉ ra rằng trong dữ liệu thực tế có rất nhiều cấu trúc dạng cây
    • JSON
    • AST
    • hệ thống tệp
    • sơ đồ tổ chức
    • XML
  • Trong những lĩnh vực này, việc kết hợp cú pháp kiểu CSS vốn ngầm xử lý quan hệ cha/con với đệ quy điểm cố định có thể rất hữu ích
    • Datalog thông thường khá bất tiện vì phải chuyển cấu trúc cây sang biểu diễn quan hệ
    • Nếu mang trực tiếp cảm giác selector của CSS vào truy vấn đệ quy, sẽ có nhiều lập trình viên tiếp cận dễ hơn
  • Dạng công cụ như vậy hiện vẫn chưa hiện ra rõ ràng
    • Tên gọi "CSSLog" chỉ là tạm thời, và có thể sẽ xuất hiện một ngôn ngữ với cái tên tốt hơn
    • Vẫn còn không gian để xử lý truy vấn cây đệ quy bằng ký pháp quen thuộc hơn

Các luận điểm bổ sung và liên kết tham khảo

  • Datalog xuất hiện từ thập niên 1970 trong bối cảnh cơ sở dữ liệu quan hệ và nghiên cứu AI thời đó, rồi sau này nhiều lần quay trở lại dưới các hình thức khác nhau
  • Một dạng đơn giản của tính toán điểm cố định thường được giới thiệu là naive evaluation, nhưng có thể kém hiệu quả vì cứ lặp lại việc tính các sự kiện đã biết
    • Semi-naive evaluation, chỉ tận dụng các sự kiện mới sinh ra ở mỗi bước, được nhắc tới như hướng cải tiến tiêu biểu
  • Tính đơn điệu cũng dẫn tới những tính chất hữu ích trong hệ phân tán
  • Cũng có cách mô phỏng một phần transitive dark mode bằng kế thừa custom property
    • [data-theme="dark"] { --effective-theme: dark; }
    • [data-theme="light"] { --effective-theme: light; }
    • @container style(--effective-theme: dark) { :focus { outline-color: white; } }
    • Cách này nhìn chung hoạt động cho trường hợp cụ thể này, nhưng không cung cấp bao đóng bắc cầu thực sự theo nghĩa tổng quát

1 bình luận

 
Ý kiến trên Hacker News
  • Bộ chọn CSS dễ viết hơn XPath rất nhiều
    Gần đây cũng có một bài nói về việc API DOM mới của PHP cho phép xử lý HTML và bộ chọn CSS một cách native rất dễ dàng. Trước đây phải chuyển CSS sang XPath
    [1] https://speakerdeck.com/keyvan/parsing-html-with-php-8-dot-4...
    Vì nó đã phát triển xoay quanh việc tạo kiểu cho trình duyệt, nên khá tiếc là không có các tính năng như chọn dựa trên nội dung văn bản kiểu XPath
    Tôi nhớ là trước đây đã từng có đề xuất, nhưng do có thể gây ra vấn đề hiệu năng trong ngữ cảnh render của trình duyệt nên không được đưa vào đặc tả

    • LLM cũng xử lý bộ chọn CSS khá tốt
      Khi làm một agent chỉnh sửa tài liệu, tôi hiển thị tài liệu dưới dạng HTML và để LLM chỉ định CSS selector để kéo những mảnh cần thiết vào ngữ cảnh, và nó hoạt động gần như kỳ diệu
    • Ở phía client, querySelector/querySelectorAll đã được dùng quá rộng rãi, nên thật vui khi DOM mới của PHP giờ cũng có thứ đó
      Mọi người có thể dùng đúng theo cách quen thuộc của mình
  • Sẽ hay hơn nếu có một cái tên để gọi tách biệt giữa cú pháp CSS và toàn bộ hệ thống gồm quy tắc, hàm, đơn vị... do CSSWG định nghĩa
    Mảng này có khá nhiều tiềm năng, nhưng nếu muốn bàn hoặc tìm hiểu các trường hợp ứng dụng khác thì có vẻ cuối cùng vẫn chỉ còn cách lục mã trên GitHub có nhúng parser CSS để xem mọi người đang làm ra những thứ kỳ lạ nào
    Tôi cũng đang nghịch một thứ giống engine template kỳ quặc, trộn giữa một ngôn ngữ markup nhẹ dựa trên node, bộ chọn CSS để biểu đạt thứ gì sẽ đi vào template, và một cú pháp na ná CSS để điều khiển cách ghép các mảnh này lại với nhau

    • Tôi nghĩ trong tiêu chuẩn thì chúng đã được tách khá rõ rồi
      https://www.w3.org/TR/selectors-3/
      Đặc tả DOM cũng tham chiếu tới nó
      https://dom.spec.whatwg.org/#selectors
      Vì vậy cách gọi chung CSS selector đã là đúng sẵn rồi, hoặc chỉ gọi là selector cũng được
      Tên DOM selector có thể nghe gọn hơn, nhưng nếu tính cả selector dùng trong CSS tĩnh hay trong các engine DOM khác ngoài JS engine (XML parser, PHP DOM API, v.v.) thì ngược lại có thể còn gây rối hơn
      Ngoài ra cũng có những selector đặc biệt gắn trực tiếp với việc render/điều hướng của trình duyệt như :hover hay ::target-text
      Tuy vậy, sẽ hữu ích nếu có một tên riêng cho tập con cú pháp truy vấn tối thiểu ít gắn với trình duyệt hoặc CSS hơn
  • Tôi nhớ tới https://github.com/braposo/graphql-css mà tôi từng thấy ở một hội nghị trước đây
    Dù là một dự án đùa vui, nó cho thấy rất rõ rằng việc mang một pattern sang ngữ cảnh khác rồi tái sử dụng có thể mở ra những điều bất ngờ

    • Cái này thú vị đấy
      Tôi đúng là đang thử mang pattern giữa các ngữ cảnh khác nhau theo kiểu như vậy
      Dù phần lớn sẽ chẳng đi đến đâu, nhưng theo tinh thần hacker thì khá là hấp dẫn
  • pyastgrep như ở https://pyastgrep.readthedocs.io/en/latest/ cho phép dùng bộ chọn CSS để truy vấn cú pháp Python
    Mặc định là XPath, ví dụ có thể dùng như pyastgrep --css 'Call > func > Name#main'

    • Cái này hay thật
      Nó gần như chạm đúng chính xác hướng mà tôi muốn chỉ tới
  • Tôi không rõ thứ này giải quyết kịch bản nào
    Ngay bây giờ cũng đã có thể thay đổi cha một cách có điều kiện dựa trên con. Ví dụ pre có padding mặc định là 16px, và nếu con trực tiếp là code thì có thể dùng &:has(> code) để đưa về 0

    • Thực ra ban đầu chuyện này xuất phát từ việc hai ý tưởng khác nhau trông có vẻ giống nhau, và tôi chỉ đang thử đẩy mối liên hệ đó theo nhiều hướng hơn
      Kết luận cũng không phải là "phải sửa giới hạn của CSS hiện đại", mà gần hơn với việc liệu có thể đặt một cú pháp giống CSS lên trên một hệ thống giống Datalog để khiến việc làm việc với dữ liệu dạng cây trở nên quen thuộc hơn với nhiều kỹ sư hay không
    • Điều đang nói ở đây không phải là giải quyết bằng một lần tính style, mà là cú pháp để chỉnh sửa chính dữ liệu nền của đối tượng khớp với selector
      Tức là đang nói tới chuyện thêm phần tử con hoặc thuộc tính mới vào DOM
  • LLM hiện tại thật ra không giỏi xử lý CSS lắm, nên ngược lại tôi lại muốn thử việc này để xem liệu nó có giúp LLM suy luận đơn giản hơn không

  • Chưa nghĩ ra giá trị sử dụng thực tế nào rõ ràng, nhưng dù sao vẫn ngầu

  • Ừm... tôi thấy cái này chẳng phải chỉ là JQ sao

  • Tôi có thích CSS ở một mức độ nào đó, nhưng ghét việc complexity creep ngày càng nặng
    Tôi hiểu lập luận rằng ngôn ngữ lập trình sẽ mạnh hơn ngôn ngữ không phải lập trình, nhưng thay vì cứ tiếp tục làm HTML, CSS và JavaScript ngày càng phức tạp hơn, tôi cảm thấy thà có một thứ khác thay thế toàn bộ còn hơn
    Tôi cũng gần như không dùng các phần tử mới của HTML5 vì phần lớn tôi không hiểu chúng cần để làm gì. Cuối cùng tôi lại nghĩ nhiều container chỉ đơn giản là div có ID riêng, thậm chí còn từng muốn có một kiểu bí danh cho các ID đó để điều hướng href nội bộ
    Những thứ như [data-theme="dark"] [data-theme="light"] :focus { outline-color: black; } làm tôi mất quá nhiều thời gian để diễn giải trong đầu nên tôi không còn thấy nó thanh lịch và đơn giản nữa
    Trong khi đó h2 { color: red; } thì vẫn đơn giản
    Những biểu thức như ancestor(X, Y) :- parent(X, Y). thì tôi đã không muốn nghĩ tới rồi. :- rốt cuộc là cái gì vậy, nhìn như mặt cười
    Tôi đã dừng đọc ở @container style(--theme: dark) { .card { background: royalblue; color: white; } }
    Thật kỳ lạ khi một tiêu chuẩn từng hoạt động tốt lại có vẻ như ngày càng trở nên hỏng hóc theo thời gian

    • Ý tôi không phải là thêm cú pháp và ngữ nghĩa vào CSS, mà đúng hơn là lấy cắp ý tưởng từ CSS rồi tận dụng sự tương đồng với ngôn ngữ truy vấn logic/quan hệ để tạo ra một thứ mới
      Ví dụ, nếu diễn giải [data-theme="dark"] [data-theme="light"] :focus { outline-color: black; } thành giả mã kiểu tiếng Anh, thì nó gần với ý: nếu có X với data-theme="dark", và con của nó là Y với data-theme="light" đang ở trạng thái focus, thì đặt outline-color của Y thành black
      Vì vậy có thể viết kiểu Datalog là outline-color(Y, black) if data-theme(X, "dark") and parent(X, Y) and data-theme(Y, "light") and focused(Y)
      Tức là thay :- bằng if, và dấu phẩy bằng and
      Đi xa hơn nữa, cũng có thể viết như Y.outline_color := black if X.data-theme == dark and Y.parent == X and Y.data-theme == dark and Y.focused để làm cho attr(X, val) trông giống syntactic sugar kiểu UFCS như X.attr == val
      Nếu muốn trông giống họ ALGOL hơn nữa thì cũng có thể viết như forall Y { Y.outline_color := black if Y.data_theme == "dark" and Y.focused and Y.parent.data_theme == "light" }
      Ở đây Y được đưa vào một cách tường minh và một phép join được ngầm hóa để trông giống lập trình phổ thông hơn, nhưng trên thực tế, engine Datalog chỉ đang chạy kiểu vòng lặp này một cách hiệu quả mỗi khi phụ thuộc thay đổi thôi`