- selectors और rules के ज़रिए target set चुनकर properties लागू करने वाली CSS की संरचना, sets और rules पर काम करने वाले Datalog से रूपात्मक रूप से मिलती-जुलती है
div.awesomeजैसे selector combination intersection बनाते हैं, और Datalog में एक ही variable को दोहराने के तरीके से वैसा ही join होता है- मौजूदा CSS computed style के परिणाम को फिर से selection condition के रूप में इस्तेमाल नहीं कर सकती, इसलिए recursive transitive queries या derived state के बार-बार propagation को सीधे व्यक्त करना कठिन है
- Datalog recursive rules और fixpoint evaluation के ज़रिए relations को तब तक बढ़ाता है जब तक नए facts बनना बंद न हो जाएँ, और monotonicity की वजह से सीमित दायरे में computation समाप्त हो सकती है
- वास्तविक CSS में Container Queries जैसी सुविधाओं से ancestor की जानकारी पढ़ी जा सकती है, लेकिन यह feedback loop और cycle को रोकने की दिशा चुनती है; इसके बावजूद CSS syntax को recursive queries से जोड़ने की गुंजाइश बनी हुई है
CSS और Datalog की मिलती-जुलती संरचना
- CSS में target set selection और चुने गए targets पर rule application जैसी संरचना होती है
- HTML elements जैसी "Things" पहले से मौजूद होती हैं, और selector समान गुणों वाले set की ओर इशारा करता है
div,#child,.awesome,[data-custom-attribute="foo"]जैसे selectors से set का वर्णन किया जा सकता हैdiv.awesomeकी तरह selectors को जोड़कर intersection बनाया जा सकता है
- CSS rule selector और declaration को बाँधकर चुने गए elements पर
colorयाfont-sizeजैसी properties सेट करता है- लेकिन ऐसी properties आम तौर पर भाषा के बाहर की state बदलती हैं, और उसके परिणाम को फिर से selector condition नहीं बनाया जा सकता
div[color=red]की तरह style result को दोबारा query करने वाला रूप browser स्वीकार नहीं करता
- Datalog भी इसी तरह facts के set और rule-based derivation से काम करता है
parent(alice, bob)जैसे atoms और relations इसकी मूल इकाइयाँ हैं- variables
X,Yका उपयोग करके condition से मेल खाने वाले items का set चुना जा सकता है - एक ही variable को दोहराकर conditions जोड़ने पर CSS के selector combination जैसा join होता है
head(X, Y) :- body1(X, Z), body2(Z, Y)संरचना, दिशा उलटी होने के अलावा, CSS rule से मिलती-जुलती है- CSS का selector Datalog के body के अधिक करीब है, और declaration head के करीब है
div.awesome { color: red; }का समकक्षcolor(X, red) :- div(X), class(X, awesome).है
recursive queries जो CSS नहीं कर पाती
data-theme="dark"के भीतर आने वाले सभी focused elements पर inverted style लागू करना, लेकिन बीच मेंdata-theme="light"आ जाए तो रुक जाना, ऐसी condition के लिए transitive query चाहिए- वास्तविक CSS में
[data-theme="dark"] :focusऔर[data-theme="dark"] [data-theme="light"] :focusजैसे rules से केवल कुछ हिस्सा संभाला जा सकता है - nesting level बढ़ने पर rules लगातार जोड़ने पड़ते हैं, और recursive relation को सीधे व्यक्त करना कठिन है
- वास्तविक CSS में
- ज़रूरी condition यह है कि किसी element के effectively-dark होने का recursive निर्णय किया जाए
- अगर वह स्वयं
data-theme="dark"है, तो वह effectively-dark है - effectively-dark ancestor के नीचे आने वाला child भी, यदि बीच में
data-theme="light"न हो, effectively-dark बन जाता है - इसी state के आधार पर
.effectively-dark :focusपर style लागू होना चाहिए
- अगर वह स्वयं
- काल्पनिक CSSLog syntax में rules
class: +effectively-darkकी तरह derived state जोड़ सकते हैं.effectively-dark > :not([data-theme="light"])child तक state propagate करता है- rules को target state तक पहुँचने तक recursively repeat होना पड़ेगा
- इस तरह का recursive propagation मौजूदा CSS में व्यक्त करना कठिन है
- लेख के अंत में कुछ मिलते-जुलते तरीके भी आते हैं, लेकिन वे उसी सिद्धांत का सामान्य समाधान नहीं हैं
Datalog में recursion और fixpoint
- Datalog, मौजूदा facts से नए facts derive करने के तरीके से काम करता है और recursion को मूल रूप से संभालता है
ancestor(X, Y) :- parent(X, Y).ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
ancestorrule parent relation के आधार पर ancestor relation को चरण-दर-चरण बढ़ाता हैparent(alice, bob)से पहलेancestor(alice, bob)बनता है- फिर
alice -> bob -> carol,alice -> bob -> daveजैसे paths भी अतिरिक्त रूप से derive होते हैं
- यह computation explicit
forloop के बिना भी fixpoint evaluation से अंत तक चलती है- शुरुआत में केवल घोषित base facts का उपयोग होता है
- सभी rules के body को मौजूदा facts set पर लागू करके head जोड़ा जाता है
- जब नए facts बनना बंद हो जाते हैं, तब प्रक्रिया रुक जाती है
- यह तरीका समाप्त हो जाता है क्योंकि इसमें monotonicity होती है
- facts केवल जोड़े जाते हैं, हटाए नहीं जाते, इसलिए ज्ञात facts का set लगातार बड़ा ही होता है
- यदि शुरुआत सीमित facts set से हो, तो derive किए जा सकने वाले facts की संख्या भी सीमित रहती है
- इसके उलट, facts हटाए जा सकें तो पहले के निष्कर्ष पलट सकते हैं और system infinite loop में फँस सकता है
Container Queries और वास्तविक CSS की सीमाएँ
- वास्तविक CSS की Container Queries ancestor या container के style के आधार पर rules लागू कर सकती हैं
- यह
@container style(--theme: dark) { .card { background: royalblue; color: white; } }जैसे रूप को support करती है
- यह
- लेकिन transitive dark mode वाला उदाहरण, साधारण ancestor lookup से अधिक मजबूत condition माँगता है
- हर element को यह जानना होगा कि वह स्वयं effectively-dark है या नहीं
- यह state उसके पूरे descendants तक transitively propagate होनी चाहिए
data-theme="light"की boundary पर propagation रुक जाना चाहिए
- Container Queries दूसरी condition को संभाल नहीं पातीं
- ancestor की custom property पढ़ी जा सकती है, लेकिन किसी दूसरे rule द्वारा पहले से compute की गई derived state को फिर से query नहीं किया जा सकता
- DOM में मूल रूप से मौजूद जानकारी देखी जा सकती है, लेकिन recursive computation के परिणाम को selector condition नहीं बनाया जा सकता
- 2015 की संबंधित पोस्ट में भी बताया गया था कि element queries इसी समस्या से टकराईं
- यदि query से set की गई property को फिर से query करने दिया जाए, तो loop और infinite repetition का जोखिम बढ़ जाता है
- CSS Working Group ने इस समस्या से information flow की दिशा पर प्रतिबंध लगाकर बचाव किया है
- descendants को ancestor की जानकारी query करने की अनुमति है
- उलटी दिशा के feedback या अपनी ही style में cycle बनने से रोका जाता है
- इसलिए fixpoint semantics के बिना भी computation सीमित रखी जा सकती है
CSS syntax को recursive query language में उलटने की संभावना
- Datalog semantics को CSS में डालने के बजाय, CSS syntax को Datalog के ऊपर रखने की दिशा एक अधिक व्यावहारिक नए रास्ते के रूप में प्रस्तुत की गई है
- Datalog का
:-, पूर्ण विराम, declaration के बिना atoms जैसी syntax आधुनिक language users के लिए entry barrier ऊँची बनाती है - CSS में tree structure संभालने के लिए पहले से ही समृद्ध selector syntax मौजूद है
- Datalog का
- लेख यह भी बताता है कि वास्तविक डेटा में tree-shaped structures बहुत आम हैं
- JSON
- AST
- filesystem
- org chart
- XML
- ऐसे क्षेत्रों में parent/child relation को implicitly संभालने वाली CSS-शैली syntax और fixpoint recursion का संयोजन उपयोगी हो सकता है
- सामान्य Datalog में tree structure को relational form में दोबारा लिखना पड़ता है, जो काफ़ी झंझट वाला है
- अगर CSS selector की समझ को ज्यों का त्यों recursive queries में लाया जाए, तो अधिक programmers इसे आसानी से अपना सकते हैं
- इस तरह का tool अभी स्पष्ट रूप से दिखाई नहीं देता
- "CSSLog" नाम फिलहाल अस्थायी है, और भविष्य में बेहतर नाम वाली language आ सकती है
- recursive tree queries को अधिक परिचित notation में संभालने की गुंजाइश अभी भी बनी हुई है
पूरक बिंदु और संदर्भ लिंक
- Datalog 1970 के दशक से relational databases और उस समय के AI research के संदर्भ में उभरा था, और बाद में कई रूपों में बार-बार सामने आता रहा
- fixpoint computation का एक सरल रूप naive evaluation के रूप में प्रस्तुत किया जाता है, लेकिन यह हर बार पहले से ज्ञात facts को फिर से compute करके अप्रभावी हो सकता है
- हर चरण में केवल नए निकले facts का उपयोग करने वाला semi-naive evaluation एक प्रमुख सुधार दिशा के रूप में साथ में उल्लेखित है
- monotonicity, distributed systems में भी उपयोगी गुण साबित होती है
- custom property inheritance से transitive dark mode की आंशिक नकल करने का एक तरीका भी है
[data-theme="dark"] { --effective-theme: dark; }[data-theme="light"] { --effective-theme: light; }@container style(--effective-theme: dark) { :focus { outline-color: white; } }- यह तरीका इस खास मामले में अधिकांशतः काम करता है, लेकिन वास्तविक transitive closure को सामान्य रूप से उपलब्ध नहीं कराता
1 टिप्पणियां
Hacker News टिप्पणियाँ
CSS selectors को XPath की तुलना में लिखना बहुत आसान है
हाल ही में PHP के नए DOM API पर एक प्रस्तुति भी थी, जिसमें बताया गया कि अब HTML और CSS selectors को native तौर पर बहुत आसानी से हैंडल किया जा सकता है। पहले CSS को XPath में बदलना पड़ता था
[1] https://speakerdeck.com/keyvan/parsing-html-with-php-8-dot-4...
अफ़सोस है कि यह ब्राउज़र styling-केंद्रित तरीके से विकसित हुआ, इसलिए इसमें XPath जैसी text content आधारित selection जैसी सुविधाएँ नहीं हैं
मुझे पता है कि पहले इसके प्रस्ताव आए थे, लेकिन ब्राउज़र rendering context में performance समस्याएँ आ सकती थीं, इसलिए शायद यह spec में शामिल नहीं हो पाया
एक document editing agent बनाते समय मैंने दस्तावेज़ को HTML के रूप में दिखाया और LLM से सिर्फ़ CSS selector निर्दिष्ट करवाकर ज़रूरी हिस्से context में खींचने दिए, और यह लगभग जादू की तरह काम करता था
लोग उसी परिचित तरीके से इसे इस्तेमाल कर सकते हैं
काश CSS grammar और CSSWG द्वारा परिभाषित rules, functions, units जैसी पूरी प्रणाली को अलग से बुलाने के लिए कोई नाम होता
इसमें काफ़ी संभावना है, लेकिन दूसरे use cases पर बात करने या उन्हें खोजने के लिए आख़िरकार GitHub के उस कोड को खंगालना पड़ता है जिसमें CSS parser शामिल हो, ताकि पता चल सके कि लोग कैसी अजीब चीज़ें बना रहे हैं
मैं कुछ वैसा भी छेड़छाड़ कर रहा हूँ जो किसी अजीब template engine जैसा है, जिसमें हल्की node-आधारित markup language, template में क्या जाएगा यह व्यक्त करने वाले CSS selectors, और इन टुकड़ों को कैसे जोड़ा जाए इसे नियंत्रित करने वाला CSS-जैसा grammar मिला हुआ है
https://www.w3.org/TR/selectors-3/
DOM spec भी इसी का संदर्भ देती है
https://dom.spec.whatwg.org/#selectors
इसलिए CSS selector एक umbrella term के रूप में पहले से सही है, और इसे सिर्फ़ selector भी कहा जा सकता है
DOM selector नाम और साफ़ लग सकता है, लेकिन अगर static CSS में इस्तेमाल होने वाले selectors या JS engine के बाहर के दूसरे DOM engines (XML parser, PHP DOM API आदि) के selectors को भी सोचें, तो यह उल्टा और भ्रमित कर सकता है
साथ ही
:hoverया::target-textजैसे ऐसे special selectors भी हैं जो सीधे ब्राउज़र rendering/navigation से जुड़े हैंफिर भी, ब्राउज़र या CSS से कम जुड़े हुए न्यूनतम query grammar subset के लिए अलग नाम होना उपयोगी हो सकता है
मुझे पुराने conference में देखा हुआ https://github.com/braposo/graphql-css याद आ गया
यह मज़ाकिया project था, लेकिन यह अच्छी तरह दिखाता था कि patterns को दूसरे contexts में transplant करके दोबारा इस्तेमाल करने का तरीका अप्रत्याशित चीज़ों को संभव बना सकता है
मैं भी ठीक इसी तरह अलग contexts के patterns उठाकर आज़माने की कोशिश कर रहा हूँ
ज़्यादातर चीज़ें शायद कहीं दूर तक न जाएँ, फिर भी hacker संवेदना के हिसाब से यह काफ़ी दिलचस्प है
pyastgrep में, जैसा कि https://pyastgrep.readthedocs.io/en/latest/ पर दिखता है, Python grammar को query करने के लिए CSS selectors इस्तेमाल किए जा सकते हैं
default XPath है, और उदाहरण के लिए
pyastgrep --css 'Call > func > Name#main'जैसा इस्तेमाल संभव हैयह लगभग ठीक उसी दिशा से मेल खाता है जिसकी ओर मैं इशारा करना चाहता था
मुझे ठीक से समझ नहीं आ रहा कि यह किस scenario को हल करता है
अभी भी child के आधार पर parent को conditionally बदला जा सकता है। उदाहरण के लिए
preकी default padding 16px है, और अगर उसका direct childcodeहै तो&:has(> code)से इसे 0 किया जा सकता हैनिष्कर्ष भी "आधुनिक CSS की सीमाओं को ठीक करना चाहिए" से ज़्यादा इस बात के करीब है कि शायद CSS-जैसा grammar किसी Datalog-जैसी system पर चढ़ाने से tree-आकृति वाले data को संभालना ज़्यादा इंजीनियरों के लिए परिचित बनाया जा सकता है
यानी बात DOM में नए child elements या attributes जोड़ने की है
मौजूदा LLM CSS को बहुत अच्छी तरह नहीं संभालते, इसलिए उल्टा यह आज़माकर देखना दिलचस्प होगा कि क्या इससे LLM ज़्यादा सरल तरीके से reason कर पाते हैं
इसका कोई वास्तविक उपयोग तुरंत नहीं सूझता, लेकिन यह फिर भी काफ़ी cool है
हम्म... कहीं यह बस JQ तो नहीं है?
मुझे CSS कुछ हद तक पसंद है, लेकिन इसकी बढ़ती हुई complexity creep पसंद नहीं
मैं समझता हूँ कि programming languages, non-programming languages से ज़्यादा शक्तिशाली हो जाती हैं, लेकिन HTML, CSS और JavaScript को लगातार और जटिल बनाने की बजाय शायद बेहतर होता कि कुछ और आकर पूरे सेट को replace कर देता
HTML5 के नए elements भी क्यों ज़रूरी हैं, यह मुझे ज़्यादातर समझ नहीं आता, इसलिए मैं उन्हें शायद ही कभी इस्तेमाल करता हूँ। आख़िर में कई containers बस unique ID लगे
divही लगते हैं, और कभी-कभी लगता था कि internal link के लिएhrefnavigation हेतु उन IDs के aliases जैसे कुछ होने चाहिए थे[data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }जैसी चीज़ को दिमाग़ में parse करने में बहुत समय लगता है, इसलिए यह अब elegant या simple नहीं लगतीदूसरी तरफ़
h2 { color: red; }अब भी simple हैancestor(X, Y) :- parent(X, Y).जैसा expression देखते ही सोचने का मन नहीं करता।:-आख़िर है क्या, मुस्कुराते चेहरे जैसा दिखता है@container style(--theme: dark) { .card { background: royalblue; color: white; } }पर तो मैंने पढ़ना ही छोड़ दियाअजीब लगता है कि जो standard पहले ठीक काम करता था, वह समय के साथ बिगड़ता जा रहा है
उदाहरण के लिए
[data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }को अंग्रेज़ी जैसी pseudocode में खोलें तो इसका मतलब लगभग यह है कि अगरdata-theme="dark"वाला X है, उसका child Ydata-theme="light"है, और वह focus स्थिति में है, तो Y काoutline-colorblack कर दोइसलिए इसे Datalog शैली में
outline-color(Y, black) if data-theme(X, "dark") and parent(X, Y) and data-theme(Y, "light") and focused(Y)की तरह लिखा जा सकता हैयानी
:-कोifसे और comma को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-जैसी syntax sugar की तरह दिखाया जा सकेअगर इसे और ALGOL परिवार जैसा बनाना हो, तो
forall Y { Y.outline_color := black if Y.data_theme == "dark" and Y.focused and Y.parent.data_theme == "light" }जैसा भी लिखा जा सकता हैयहाँ Y को explicitly introduce किया गया है और एक join को implicit बना दिया गया है, जिससे यह ज़्यादा सामान्य programming जैसा दिखता है, लेकिन असल में Datalog engine dependencies बदलने पर हर बार ऐसे loop को efficiently चलाता है