- Struktur CSS yang memilih himpunan target dan menerapkan properti melalui selector dan rule mirip secara bentuk dengan Datalog, yang bekerja dengan himpunan dan aturan
- Penggabungan selector seperti
div.awesome membentuk irisan, dan di Datalog join serupa terjadi dengan cara mengulang variabel yang sama
- CSS saat ini tidak bisa memakai hasil style yang telah dihitung sebagai kondisi seleksi lagi, sehingga sulit mengekspresikan kueri transitif rekursif atau propagasi berulang dari status turunan secara langsung
- Datalog memperluas relasi dengan aturan rekursif dan evaluasi titik tetap sampai tidak ada fakta baru yang muncul lagi, dan berkat monotonicity perhitungannya bisa selesai dalam cakupan yang terbatas
- CSS nyata memiliki fitur seperti Container Queries untuk membaca informasi leluhur, tetapi memilih arah yang mencegah feedback loop dan siklus, meski tetap menyisakan ruang untuk menggabungkan sintaks CSS dengan kueri rekursif
Struktur mirip antara CSS dan Datalog
- CSS memiliki struktur berupa memilih himpunan target dan menerapkan aturan pada target yang dipilih
- “Things” seperti elemen HTML ada lebih dulu, lalu selector menunjuk himpunan dengan sifat yang sama
- Himpunan bisa dideskripsikan dengan selector seperti
div, #child, .awesome, [data-custom-attribute="foo"]
- Selector bisa digabung seperti
div.awesome untuk membentuk irisan
- Aturan CSS mengikat selector dan declaration untuk menetapkan properti seperti
color atau font-size pada elemen yang dipilih
- Namun properti ini umumnya mengubah status di luar bahasa itu sendiri, dan hasilnya tidak bisa dipakai lagi sebagai kondisi selector
- Bentuk seperti
div[color=red] yang mengueri ulang hasil style tidak diterima browser
- Datalog bekerja dengan cara yang serupa, yakni melalui himpunan fakta dan penurunan berbasis aturan
- Atom dan relation seperti
parent(alice, bob) menjadi unit dasarnya
- Variabel
X, Y digunakan untuk memilih himpunan item yang cocok dengan kondisi
- Jika variabel yang sama diulang untuk menghubungkan kondisi, terjadi join yang mirip dengan penggabungan selector di CSS
- Struktur
head(X, Y) :- body1(X, Z), body2(Z, Y) mirip dengan aturan CSS, hanya arahnya terbalik
- Selector pada CSS lebih dekat ke body Datalog, dan declaration lebih dekat ke head
div.awesome { color: red; } berpadanan dengan color(X, red) :- div(X), class(X, awesome).
Kueri rekursif yang tidak bisa dilakukan CSS
- Kondisi untuk memberi style terbalik pada semua elemen fokus di dalam
data-theme="dark", tetapi berhenti jika data-theme="light" muncul di tengah, memerlukan kueri transitif
- Dalam CSS nyata, hal ini hanya bisa ditangani sebagian dengan aturan seperti
[data-theme="dark"] :focus dan [data-theme="dark"] [data-theme="light"] :focus
- Jika tingkat nested bertambah, aturan harus terus ditambah, dan sulit mengekspresikan relasi rekursif secara langsung
- Kondisi yang dibutuhkan adalah menentukan secara rekursif apakah sebuah elemen effectively-dark
- Jika dirinya sendiri memiliki
data-theme="dark", maka ia menjadi effectively-dark
- Anak di bawah leluhur effectively-dark juga menjadi effectively-dark, selama tidak ada
data-theme="light" di tengah
- Berdasarkan status ini, style harus diterapkan ke
.effectively-dark :focus
- Dalam sintaks CSSLog hipotetis, aturan bisa menambahkan status turunan seperti
class: +effectively-dark
.effectively-dark > :not([data-theme="light"]) akan mempropagasikan status ke anak
- Aturan harus diulang secara rekursif sampai mencapai status target
- Jenis propagasi rekursif seperti ini sulit diekspresikan dalam CSS saat ini
- Di bagian akhir tulisan juga muncul beberapa cara untuk menirunya secara terbatas, tetapi itu bukan solusi umum dengan prinsip yang sama
Rekursi dan titik tetap di Datalog
- Datalog bekerja dengan cara menurunkan fakta baru dari fakta yang sudah ada, dan secara bawaan menangani rekursi
ancestor(X, Y) :- parent(X, Y).
ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
- Aturan
ancestor memperluas relasi leluhur secara bertahap berdasarkan relasi orang tua
- Dari
parent(alice, bob) mula-mula muncul ancestor(alice, bob)
- Lalu jalur seperti
alice -> bob -> carol, alice -> bob -> dave juga diturunkan lebih lanjut
- Perhitungan ini berjalan sampai selesai melalui evaluasi titik tetap tanpa perlu loop
for eksplisit
- Pada awalnya hanya fakta dasar yang dinyatakan yang digunakan
- Body dari semua aturan diterapkan ke himpunan fakta saat ini untuk menambahkan head
- Proses berhenti ketika tidak ada fakta baru yang muncul lagi
- Alasan pendekatan ini selesai terletak pada monotonicity
- Sistem hanya menambahkan fakta dan tidak menghapusnya, sehingga himpunan fakta yang diketahui hanya terus membesar
- Jika dimulai dari himpunan fakta yang terbatas, jumlah fakta yang bisa diturunkan juga terbatas
- Sebaliknya, jika fakta bisa dihapus, kesimpulan sebelumnya bisa terbalik dan jatuh ke loop tak berujung
Container Queries dan batas CSS nyata
- Container Queries di CSS nyata memungkinkan aturan diterapkan berdasarkan style leluhur atau container
- Bentuk seperti
@container style(--theme: dark) { .card { background: royalblue; color: white; } } didukung
- Namun contoh transitive dark mode memerlukan kondisi yang lebih kuat daripada sekadar membaca leluhur
- Setiap elemen harus tahu apakah dirinya sendiri effectively-dark
- Status itu harus dipropagasikan secara transitif ke seluruh turunannya
- Propagasi harus berhenti di batas
data-theme="light"
- Container Queries tidak bisa menangani syarat kedua
- Custom property dari leluhur memang bisa dibaca, tetapi status turunan yang sudah dihitung oleh aturan lain tidak bisa dikueri ulang
- Informasi yang memang sudah ada di DOM bisa dilihat, tetapi hasil perhitungan rekursif tidak bisa dijadikan kondisi selector
- Tulisan terkait dari 2015 juga menyoroti bahwa element queries menabrak masalah yang sama
- Jika properti yang ditetapkan lewat kueri bisa dikueri lagi, risiko loop dan pengulangan tak berujung akan membesar
- CSS Working Group selama ini menghindari masalah ini dengan membatasi arah aliran informasi
- Turunan boleh mengueri informasi dari leluhur
- Feedback ke arah sebaliknya atau siklus ke style dirinya sendiri dicegah
- Karena itu, perhitungan bisa tetap terbatas tanpa semantik titik tetap
Kemungkinan membalik sintaks CSS menjadi bahasa kueri rekursif
- Alih-alih memasukkan semantik Datalog ke CSS, arah yang lebih realistis adalah meletakkan sintaks CSS di atas Datalog
- Sintaks seperti
:-, titik, dan atom tanpa deklarasi di Datalog menjadi hambatan masuk bagi banyak pengguna bahasa modern
- CSS sudah memiliki sintaks selector yang kaya untuk menangani struktur pohon
- Banyak data di dunia nyata berbentuk struktur pohon
- JSON
- AST
- filesystem
- bagan organisasi
- XML
- Di area seperti ini, menggabungkan sintaks bergaya CSS yang menangani relasi induk/anak secara implisit dengan rekursi titik tetap bisa berguna
- Datalog umum mengharuskan struktur pohon ditulis ulang ke representasi relasional, yang cukup merepotkan
- Jika intuisi selector CSS dibawa langsung ke kueri rekursif, lebih banyak programmer bisa mendekatinya dengan mudah
- Alat dalam bentuk seperti ini belum benar-benar terlihat jelas
- Nama “CSSLog” masih bersifat sementara, dan bisa saja nanti muncul bahasa dengan nama yang lebih baik
- Masih ada ruang untuk menangani kueri pohon rekursif dengan notasi yang lebih akrab
Poin tambahan dan tautan referensi
- Datalog muncul sejak 1970-an dalam konteks basis data relasional dan riset AI masa itu, lalu berulang kali muncul kembali dalam berbagai bentuk
- Bentuk sederhana dari perhitungan titik tetap diperkenalkan sebagai naive evaluation, tetapi bisa tidak efisien karena fakta yang sudah diketahui dihitung ulang setiap saat
- Semi-naive evaluation, yang hanya memanfaatkan fakta baru pada tiap tahap, disebut sebagai arah perbaikan yang mewakili
- Monotonicity juga menghasilkan sifat yang berguna dalam sistem terdistribusi
- Ada juga cara meniru transitive dark mode secara parsial dengan pewarisan custom property
[data-theme="dark"] { --effective-theme: dark; }
[data-theme="light"] { --effective-theme: light; }
@container style(--effective-theme: dark) { :focus { outline-color: white; } }
- Untuk kasus khusus ini cara tersebut umumnya bekerja, tetapi tidak benar-benar menyediakan transitive closure secara umum
1 komentar
Komentar Hacker News
Selector CSS jauh lebih mudah ditulis daripada XPath
Baru-baru ini bahkan ada presentasi tentang API DOM baru di PHP yang memudahkan penanganan HTML dan selector CSS secara native. Dulu CSS harus dikonversi ke XPath
[1] https://speakerdeck.com/keyvan/parsing-html-with-php-8-dot-4...
Karena berkembang dengan fokus pada styling browser, agak disayangkan tidak ada fitur seperti pemilihan berdasarkan isi teks seperti di XPath
Setahu saya dulu pernah ada usulan, tetapi tidak masuk ke spesifikasi karena bisa menimbulkan masalah performa dalam konteks rendering browser
Saat membuat agen pengedit dokumen, saya menampilkan dokumen sebagai HTML lalu membiarkan LLM hanya menentukan CSS selector untuk menarik fragmen yang dibutuhkan ke dalam konteks, dan hasilnya bekerja nyaris seperti sihir
Orang-orang bisa memakainya dengan cara yang sudah mereka kenal
Akan bagus kalau ada nama terpisah untuk membedakan sintaks CSS dan keseluruhan sistem seperti aturan, fungsi, dan unit yang didefinisikan CSSWG
Ada cukup banyak potensi di sini, tetapi untuk membahas atau meneliti kasus penggunaan lain, sepertinya pada akhirnya kita harus mengaduk-aduk kode di GitHub yang memakai parser CSS untuk melihat hal-hal aneh apa yang dibuat orang
Saya juga sedang mengutak-atik sesuatu yang mirip mesin template aneh, yang mencampurkan bahasa markup ringan berbasis node, selector CSS untuk menyatakan apa yang masuk ke template, dan sintaks mirip CSS untuk mengendalikan bagaimana potongan-potongan ini digabungkan
https://www.w3.org/TR/selectors-3/
Spesifikasi DOM juga merujuk ke sini
https://dom.spec.whatwg.org/#selectors
Jadi sebutan umum CSS selector memang sudah tepat, dan boleh juga hanya disebut selector
Nama DOM selector mungkin terdengar lebih rapi, tetapi jika memikirkan selector yang dipakai di CSS statis atau di engine DOM lain di luar engine JS, seperti XML parser atau PHP DOM API, itu justru bisa lebih membingungkan
Selain itu ada juga selector khusus seperti
:hoveratau::target-textyang terikat langsung dengan rendering dan navigasi browserNamun, untuk subset sintaks query minimum yang kurang terikat ke browser atau CSS, akan berguna jika ada nama tersendiri
Ini mengingatkan saya pada https://github.com/braposo/graphql-css yang pernah saya lihat di konferensi dulu
Itu proyek bercanda, tetapi saya suka karena menunjukkan dengan baik bahwa memindahkan pola ke konteks lain dan memakainya kembali bisa memungkinkan hal-hal yang tak terduga
Saya memang sedang mencoba membawa pola dari konteks berbeda seperti itu
Meski kebanyakan mungkin tidak akan ke mana-mana, dari sudut pandang hacker ini cukup menarik
pyastgrep, seperti terlihat di https://pyastgrep.readthedocs.io/en/latest/, bisa memakai selector CSS untuk meng-query sintaks Python
Default-nya adalah XPath, dan misalnya bisa seperti
pyastgrep --css 'Call > func > Name#main'Hampir persis mengarah ke hal yang ingin saya tunjuk
Saya kurang paham skenario apa yang diselesaikan ini
Saat ini pun parent bisa diubah secara kondisional berdasarkan child. Misalnya
prepunya padding default 16px, dan jika child langsungnya adalahcode, itu bisa dijadikan 0 dengan&:has(> code)Jadi kesimpulannya lebih dekat ke "memakai sintaks mirip CSS di atas sistem mirip Datalog mungkin bisa membuat pekerjaan menangani data berbentuk tree terasa lebih akrab bagi lebih banyak engineer" daripada "memperbaiki keterbatasan CSS modern"
Artinya, pembicaraannya adalah tentang menambahkan elemen child atau atribut baru ke DOM
LLM saat ini cenderung tidak begitu bagus menangani CSS, jadi malah membuat saya ingin mencoba ini untuk melihat apakah LLM bisa bernalar lebih sederhana dengannya
Saya belum terlalu melihat kegunaan praktisnya, tapi tetap keren
Hmm... bukankah ini cuma JQ?
Saya cukup suka CSS sampai titik tertentu, tetapi saya tidak suka complexity creep yang makin parah
Saya paham logika bahwa bahasa pemrograman menjadi lebih kuat daripada bahasa non-pemrograman, tetapi alih-alih terus memperbesar kerumitan HTML, CSS, dan JavaScript, rasanya lebih baik muncul sesuatu yang menggantikan semuanya
Saya juga hampir tidak pernah memakai elemen baru HTML5 karena sebagian besar saya tidak paham kenapa itu diperlukan. Akhirnya saya menganggap banyak container hanyalah
divdengan ID unik, dan saya bahkan berharap ada semacam alias untuk ID itu demi navigasihrefinternalHal seperti
[data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }juga terasa butuh terlalu lama untuk dipahami di kepala, jadi tidak lagi terasa elegan dan sederhanaSebaliknya,
h2 { color: red; }masih tetap sederhanaEkspresi seperti
ancestor(X, Y) :- parent(X, Y).saja sudah membuat saya malas berpikir.:-itu sebenarnya apa, kelihatan seperti wajah tersenyumSaya berhenti membaca di
@container style(--theme: dark) { .card { background: royalblue; color: white; } }Aneh rasanya melihat standar yang dulu bekerja baik makin lama justru seperti rusak
Misalnya
[data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }jika diurai ke pseudocode bergaya bahasa Inggris kurang lebih berarti, jika ada X dengandata-theme="dark"dan child Y dari X punyadata-theme="light"serta sedang fokus, maka setoutline-colorY menjadi blackJadi ini bisa ditulis dalam gaya Datalog sebagai
outline-color(Y, black) if data-theme(X, "dark") and parent(X, Y) and data-theme(Y, "light") and focused(Y)Artinya
:-diganti menjadiif, dan koma diganti menjadiandLebih jauh lagi, ini bisa ditulis sebagai
Y.outline_color := black if X.data-theme == dark and Y.parent == X and Y.data-theme == dark and Y.focusedsehinggaattr(X, val)tampak seperti syntactic sugar mirip UFCS semacamX.attr == valJika ingin terlihat lebih seperti keluarga ALGOL, bisa juga menjadi
forall Y { Y.outline_color := black if Y.data_theme == "dark" and Y.focused and Y.parent.data_theme == "light" }Di sini Y diperkenalkan secara eksplisit dan satu join dibuat implisit sehingga tampak lebih seperti pemrograman umum, tetapi pada praktiknya mesin Datalog yang akan menjalankan loop seperti ini secara efisien setiap kali dependensinya berubah`