มอง CSS เป็นภาษาแบบสอบถาม
(evdc.me)- ตัวเลือกและคำประกาศของ CSS มีโครงสร้างที่สอดคล้องกับ Datalog ซึ่งใช้สอบถามความสัมพันธ์และสร้างข้อเท็จจริง เพราะทั้งคู่เลือกชุดขององค์ประกอบที่มีอยู่แล้วและนำคุณสมบัติไปใช้กับผลลัพธ์นั้น
- CSS ปกติไม่รองรับ การคำนวณแบบเรียกซ้ำที่นำผลการเลือกกลับมาใช้เป็นเงื่อนไขการเลือกอีกครั้ง จึงไม่สามารถแสดงการส่งต่อสถานะโดยตรง เช่น ธีม dark ที่ถ่ายทอดไปยังลูกหลานแล้วหยุดที่ขอบเขต light
- ใน CSSLog สมมติ จะอนุญาตให้ เพิ่มคลาสที่มีผลต่อการจับคู่ตัวเลือก ทำให้สามารถส่งต่อสถานะอนุพันธ์อย่าง
.effectively-darkแบบเรียกซ้ำ และคำนวณซ้ำจนกว่าจะไม่มีผลลัพธ์ใหม่เกิดขึ้น - การคำนวณลักษณะนี้อธิบายได้ด้วย fixpoint และ monotonicity ของ Datalog โดยต้องเพิ่มข้อเท็จจริงอย่างเดียวโดยไม่ลบออก เพื่อให้การประเมินซ้ำจบลงได้ภายในเวลาจำกัด
- แม้ container queries ของ CSS จริงจะอ่านสถานะของบรรพบุรุษได้ แต่ก็ยังสอบถามสถานะอนุพันธ์แบบเรียกซ้ำไม่ได้ ดังนั้นต่อให้ CSS ขยับเข้าใกล้ Datalog มากขึ้น ก็ยังไม่ก้าวข้ามขอบเขตของเอนจินเรนเดอร์ของเบราว์เซอร์
ความสอดคล้องพื้นฐานระหว่าง CSS กับ Datalog
- CSS ตั้งอยู่บนสมมติฐานว่ามี เป้าหมายที่มีอยู่แล้ว ซึ่งในที่นี้คือองค์ประกอบ HTML
- องค์ประกอบอย่าง
h1,a,divมีอยู่ก่อนนอก CSS และ CSS ไม่ได้ประกาศสิ่งเหล่านี้ขึ้นมาใหม่ - ยกตัวอย่างองค์ประกอบที่มีแอตทริบิวต์อย่าง
class,id,data-custom-attribute
- องค์ประกอบอย่าง
- ตัวเลือกของ CSS ชี้ไปยัง เซตที่มีเงื่อนไขร่วมกัน และสามารถทำให้เป้าหมายแคบลงได้ด้วยชื่อแท็ก, id, class, ค่าของแอตทริบิวต์
- มีตัวอย่างตัวเลือกอย่าง
div,#child,.awesome,[data-custom-attribute="foo"] - ยังสามารถระบุเป้าหมายด้วยความสัมพันธ์ตำแหน่งในลำดับชั้นของเอกสาร และสร้างจุดตัดด้วยการรวมตัวเลือก
- มีตัวอย่างตัวเลือกอย่าง
- ตัวเลือกแบบรวมอย่าง
div.awesomeทำ การตัดกันของเซต โดยเลือกเฉพาะองค์ประกอบที่เป็นทั้งdivและ.awesome- แนวคิดเรื่องจุดตัดนี้จะเชื่อมต่อไปยัง join ของ Datalog ในภายหลัง
- กฎของ CSS จับคู่ตัวเลือกกับคำประกาศเพื่อ นำค่าคุณสมบัติไปใช้กับเซตที่ถูกเลือก
- ในตัวอย่าง
div.awesome { color: red; font-size: 24px }จะกำหนดcolorและfont-sizeให้กับองค์ประกอบเหล่านั้น - ในเบราว์เซอร์ ผลลัพธ์คือข้อความสีแดงขนาดใหญ่
- ในตัวอย่าง
ข้อจำกัดของ CSS และปัญหาการคิวรีแบบเรียกซ้ำ
- CSS ปกติเก่งเรื่อง เปลี่ยนคุณสมบัติที่อยู่นอกภาษา แต่ไม่สามารถนำผลของการเปลี่ยนนั้นกลับมาใช้เป็นเงื่อนไขการเลือกโดยตรงได้
- แม้จะกำหนดสีขององค์ประกอบได้ แต่ตัวอย่างอย่าง
div[color=red]ที่ใช้สีเองเป็นเงื่อนไขของตัวเลือกนั้นเบราว์เซอร์จะไม่ยอมรับ - กฎที่นำ
color: redกลับมาใช้เพื่อกำหนดcolor: blueอีกชั้นหนึ่งจึงมีความหมายไม่ชัดเจน
- แม้จะกำหนดสีขององค์ประกอบได้ แต่ตัวอย่างอย่าง
- ตัวอย่าง dark mode ใน design system ถูกยกมาเป็นปัญหาที่ต้องการ การส่งต่อสถานะแบบถ่ายทอด
- ต้องการใช้เส้นขอบโฟกัสสีขาวกับทุกองค์ประกอบแบบ interactive ภายในการ์ดที่มี
data-theme="dark" - แต่ถ้ามี
data-theme="light"อยู่ตรงกลาง การส่งต่อควรหยุดก่อนถึงด้านล่างของมัน
- ต้องการใช้เส้นขอบโฟกัสสีขาวกับทุกองค์ประกอบแบบ interactive ภายในการ์ดที่มี
- ใน CSS จริง ทำได้เพียงบางส่วนด้วย การเติมข้อยกเว้นเข้าไป
- สร้างกฎพื้นฐานด้วย
[data-theme="dark"] :focus { outline-color: white; } - แล้วแก้กลับที่ขอบเขต light ด้วย
[data-theme="dark"] [data-theme="light"] :focus { outline-color: black; } - แต่วิธีนี้ต้องเพิ่มกฎต่อไปเรื่อย ๆ เมื่อความลึกของการซ้อนมากขึ้น
- สร้างกฎพื้นฐานด้วย
- ปัญหานี้ต้องใช้ นิยามความสัมพันธ์แบบเรียกซ้ำ แต่ CSS ไม่สามารถแสดงมันได้
- จำเป็นต้องมีนิยามว่า “เป็น dark ด้วยตัวเอง หรือมีบรรพบุรุษ effectively-dark โดยไม่มีบรรพบุรุษ effectively-light คั่นอยู่ระหว่างทาง จึงถือว่า effectively-dark”
- ต้นฉบับเรียกสิ่งนี้ว่า recursive relational definition และย้ำว่า CSS แสดงสิ่งนี้ไม่ได้
ไวยากรณ์สมมติของ CSSLog
- CSSLog ถูกเสนอเป็นเวอร์ชันสมมติที่ยังคง ตัวเลือกและการตั้งค่าคุณสมบัติ แบบ CSS ทั่วไปไว้ แต่สามารถเปลี่ยนคุณสมบัติที่มีผลต่อการจับคู่ตัวเลือกได้ด้วย
- ในตัวอย่างมีไวยากรณ์อย่าง
class: +barสำหรับเพิ่มคลาส - และสมมติรูปแบบอย่าง
+<div class="baz">สำหรับสร้างองค์ประกอบลูกใหม่ - การลบองค์ประกอบมีเพียงข้อความว่า “อาจจะไม่ควรทำ” โดยไม่มีคำอธิบายเพิ่ม
- ในตัวอย่างมีไวยากรณ์อย่าง
- หลังจากกฎ
div.fooทำงานแล้ว องค์ประกอบเดียวกันอาจไปตรงกับdiv.barได้ ทำให้ ผลจากการรันกฎส่งผลต่อการจับคู่ครั้งถัดไป- ณ จุดนี้จึงไม่สามารถจบได้ด้วยการประยุกต์ไปข้างหน้าเพียงครั้งเดียว และต้องมีการคำนวณซ้ำ
- เมื่อนำตัวอย่าง dark mode ไปเขียนเป็น CSSLog จะสามารถ ส่งต่อคลาสอนุพันธ์แบบเรียกซ้ำ ได้
- เริ่มจาก
[data-theme="dark"] { class: +effectively-dark; } - ส่งต่อไปยังลูกด้วย
.effectively-dark > :not([data-theme="light"]) { class: +effectively-dark; } - แล้วจึงใช้สไตล์สุดท้ายด้วย
.effectively-dark :focus { outline-color: white; }
- เริ่มจาก
- กฎข้อที่สองจะ ส่งต่อแบบเรียกซ้ำจนกว่าจะถึงขอบเขต light และหยุดเมื่อถึงสถานะที่ต้องการ
- ต้นฉบับระบุว่า CSS ปัจจุบันทำพฤติกรรมแบบนี้ไม่ได้ และช่วงท้ายยังกลับมาพูดถึงทางอ้อมที่คล้ายกันบางส่วนอีกครั้ง
โครงสร้างของ Datalog และความคล้ายกับ CSS
- ใน Datalog เป้าหมายถูกเรียกว่า atoms และจะถือว่ามีอยู่ทันทีที่ถูกกล่าวถึงครั้งแรก
- ชื่ออย่าง
alice,bobสามารถใช้ได้ทันทีโดยไม่ต้องประกาศแยก - มีประโยคเปรียบเทียบกับ
:symbolsของ Ruby ด้วย
- ชื่ออย่าง
- เซตและความสัมพันธ์ถูกแทนด้วย relations และ tuples
parent(alice, bob)คือหนึ่ง tuple ใน relation ชื่อparentparentถูกอธิบายว่าเป็นเซตของคู่ที่ “เป้าหมายตัวแรกเป็นพ่อแม่ของเป้าหมายตัวที่สอง”
- ตัวแปรถูกใช้เพื่อ จับคู่คิวรีและเลือกเซต
parent(bob, X)หมายถึงทุกค่าXที่ Bob เป็นพ่อแม่ของมัน- ในตัวอย่างนี้
Xจะถูกประเมินเป็นcarolและdave - ตามธรรมเนียม ตัวแปรใช้ตัวพิมพ์ใหญ่ ส่วน atom และ relation ใช้ตัวพิมพ์เล็ก
- เมื่อนำชื่อตัวแปรเดียวกันมาใช้ซ้ำ จะเกิด join
mother(X, Y) :- parent(X, Y), woman(X).สร้างความสัมพันธ์แม่จากจุดตัดของเซตพ่อแม่กับเซตผู้หญิง- เนื้อหาอธิบายว่านี่คือจุดตัดของ “พ่อแม่ทั้งหมด” กับ “ผู้หญิงทั้งหมด”
:-ในกฎของ Datalog อ่านได้ว่า if โดยเมื่อเงื่อนไขทั้งหมดใน body ด้านขวาเป็นจริง ก็จะเพิ่มข้อเท็จจริงใน head ด้านซ้ายว่าเป็นจริง- เครื่องหมายจุลภาคใน body อ่านว่า and
ancestor(X, Y) :- parent(X, Y).จึงหมายความว่าถ้า X เป็นพ่อแม่ของ Y ก็ถือว่าเป็นบรรพบุรุษด้วย
- CSS กับ Datalog ถูกเปรียบเทียบว่า มีโครงสร้างคล้ายกันแต่กลับด้านกันในเชิงรูปแบบ
color(X, red) :- div(X), class(X, awesome).หมายถึง “สีของ X เป็น red ถ้า X เป็น div และมีคลาส awesome”- เนื้อหาชี้ให้เห็นความสอดคล้องเชิงความหมายกับ
div.awesome { color: red; }ของ CSS - ต้นฉบับสรุปว่า selector คือ body และ declaration คือ head
การเรียกซ้ำและข้อเท็จจริงอนุพันธ์
- ใน Datalog การ “ทำ” อะไรบางอย่างหมายถึง การอนุมานข้อเท็จจริงใหม่
- มันทำงานโดยเพิ่ม tuple ใหม่เข้าไปใน relation จากข้อเท็จจริงที่มีอยู่เดิม
- ตัวอย่าง
ancestorถูกยกเป็นกรณีคลาสสิกของ กฎแบบเรียกซ้ำancestor(X, Y) :- parent(X, Y).ทำให้ความเป็นพ่อแม่โดยตรงกลายเป็นความเป็นบรรพบุรุษancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).ขยายขึ้นไปตามความเป็นบรรพบุรุษของพ่อแม่
- กฎข้อที่สองมี การเรียกซ้ำแบบอ้างถึงตัวเอง เพราะ
ancestorปรากฏทั้งใน head และ body- ด้วยกฎนี้จึงสามารถอนุมานความสัมพันธ์บรรพบุรุษทางอ้อมอย่าง
alice -> bob -> carol,alice -> bob -> dave
- ด้วยกฎนี้จึงสามารถอนุมานความสัมพันธ์บรรพบุรุษทางอ้อมอย่าง
- ผลลัพธ์ตัวอย่างแสดงข้อเท็จจริงห้ารายการดังนี้
ancestor(alice, bob)ancestor(bob, carol)ancestor(bob, dave)ancestor(alice, carol)ancestor(alice, dave)
- SQL เองก็ทำการคำนวณลักษณะนี้ไม่ได้ก่อนมี
WITH RECURSIVEและเนื้อหาระบุว่าฟีเจอร์นี้เกิดขึ้นเพราะ ความต้องการคำนวณแบบเรียกซ้ำ- อย่างไรก็ตาม ต้นฉบับก็เสริมว่าไวยากรณ์และความหมายของการเรียกซ้ำใน SQL ไม่ได้ประกอบเข้ากับส่วนอื่นได้ดีเสมอไป
- Datalog สามารถคำนวณต่อไปได้เอง จนกว่าจะได้ผลลัพธ์ที่จำเป็นครบทั้งหมด โดยไม่ต้องมีลูป
for- ส่วนถัดไปจะเชื่อมเหตุผลนี้เข้ากับ fixpoint
Fixpoint และความเป็นโมโนโทนิก
- cascade ของ CSS ปกติถูกอธิบายว่าเป็น การประยุกต์ไปข้างหน้าเพียงครั้งเดียว
- เบราว์เซอร์อ่านกฎ คำนวณการจับคู่ตัวเลือก นำคำประกาศไปใช้ แล้วจบ
- ไม่มี feedback loop
- ใน CSSLog และ Datalog จริง ผลของกฎหนึ่งอาจทำให้เงื่อนไขของอีกกฎหนึ่งเป็นจริงอีกครั้ง
- กฎหนึ่งอาจเปลี่ยนคุณสมบัติ ทำให้กฎอื่นทำงานใหม่ และย้อนกลับมาส่งผลต่อกฎแรกอีกได้
- เอนจิน Datalog แบบตรงไปตรงมาจะ ใช้กฎซ้ำจนกว่าจะไม่มีข้อเท็จจริงใหม่เกิดขึ้นอีก
- เริ่มจาก base fact ที่ระบุไว้
- จับคู่ body ของทุกกฎกับเซตข้อเท็จจริงปัจจุบัน
- หากตรงกันก็เพิ่มข้อเท็จจริงใน head
- ถ้ามีข้อเท็จจริงใหม่ก็ทำซ้ำอีก และถ้าไม่มีแล้วก็หยุด
- จุดที่หยุดนี้เรียกว่า fixpoint
- เป็นสถานะที่แม้นำทุกกฎมาประเมินอีกก็ไม่เกิดผลลัพธ์ใหม่
- ตัวอย่าง
ancestorถูกสรุปเป็นสามรอบ- รอบแรก เพิ่มความเป็นบรรพบุรุษโดยตรงสามรายการจากข้อเท็จจริง
parent - รอบที่สอง เพิ่มความเป็นบรรพบุรุษทางอ้อมของ
aliceอีกสองรายการ - รอบที่สาม ไม่มีข้อเท็จจริงใหม่แล้ว จึงถึง fixpoint
- รอบแรก เพิ่มความเป็นบรรพบุรุษโดยตรงสามรายการจากข้อเท็จจริง
- เหตุที่หยุดได้มาจาก monotonicity
- เพราะไม่ลบข้อเท็จจริงออก มีแต่เพิ่มเข้าไป เซตของข้อเท็จจริงที่รู้จึงมีแต่ขยายใหญ่ขึ้น
- เมื่อเริ่มจากข้อเท็จจริงที่มีจำกัดและความเป็นไปได้ของข้อเท็จจริงอนุพันธ์ก็มีจำกัด ระบบจึงหยุดได้หลังทำงานไปจำนวนจำกัด
- ในทางกลับกัน หากอนุญาตให้ลบข้อเท็จจริงได้ ก็จะเกิดสถานการณ์ที่ ผลภายหลังพลิกผลก่อนหน้า ทำให้คุณสมบัตินี้พังลง
- ต้นฉบับเรียกสิ่งนี้ว่า Infinite Loop Land และใช้เหตุผลนี้เชื่อมไปว่าทำไม CSSLog จึงควรไม่อนุญาตให้ลบองค์ประกอบ
- ในระบบกระจาย monotonicity ยังเกี่ยวข้องกับ คุณสมบัติที่ทำให้ได้ความสอดคล้องโดยไม่ต้องมีการประสานงานราคาแพง ตามที่มีหมายเหตุไว้ท้ายบท
- ลิงก์ 1 เกี่ยวกับ Consistency As Logical Monotonicity: ระบบกระจายและ monotonicity
- ลิงก์บทความวิชาการที่เกี่ยวข้อง 2: เอกสารอ้างอิงเพิ่มเติม
ทำไมเรื่องนี้จึงสำคัญ
- การเปรียบเทียบ CSS กับ Datalog เผยให้เห็น โครงสร้างเดียวกันในคนละสาขา
- ทั้งคู่มีเป้าหมาย มีการคิวรีเซตของเป้าหมายนั้น และใช้ผลลัพธ์เพื่อกระทำบางอย่างหรือสร้างข้อเท็จจริงใหม่
- Datalog และ Prolog มีที่มามาตั้งแต่ทศวรรษ 1970 ใน ฐานข้อมูลเชิงสัมพันธ์และงานวิจัย AI ในยุคนั้น และหลังจากนั้นก็ถูกสร้างขึ้นใหม่ในหลายรูปแบบ
- Datomic
- Differential Datalog
- รวมถึง rule engine อีกหลายแบบ
- บทความนี้สังเกตว่า หากสร้างระบบที่ มีเป้าหมาย อธิบายเซตได้ และทำงานกับเป้าหมายนั้นได้ ระบบมักจะบรรจบไปยังจุดที่คล้ายกัน
- สาขาฐานข้อมูลและ logic programming กับฝั่งพัฒนาเว็บแบบฟรอนต์เอนด์ มักไม่ได้เชื่อมถึงกันบ่อยนัก
- และยังทิ้งท้ายด้วยความหวังว่าหากสองฝั่งเชื่อมกันมากขึ้น อาจร่วมกันสร้างสิ่งใหม่ได้
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 อ่านสถานะอนุพันธ์ไม่ได้
- แม้จะอ่านค่าของ custom property จากบรรพบุรุษได้ แต่ไม่สามารถคิวรีสถานะอย่าง effectively-dark ที่กฎอื่นคำนวณไว้แล้วได้
- มันอ่านได้เฉพาะสถานะที่มีอยู่ใน DOM อยู่แล้ว และมองไม่เห็นผลลัพธ์จากการคำนวณแบบเรียกซ้ำ
- ดังนั้นคิวรีอย่าง “ให้ใช้ถ้ามีบรรพบุรุษใดก็ตามที่เป็น dark แบบถ่ายทอด และไม่มีบรรพบุรุษ light ที่ใกล้กว่าคั่นอยู่” จึง ต้องใช้การเรียกซ้ำ และทำไม่ได้
- ต้นฉบับระบุชัดว่า container queries ไม่มี recursion
- บทความปี 2015 อธิบายภูมิหลังว่าทำไม element queries ถึงล้มเหลวด้วยเหตุผลคล้ายกันอยู่เรื่อย ๆ
- หากปล่อยให้คิวรีนำคุณสมบัติที่ตัวเองตั้งค่าไว้กลับมาคิวรีซ้ำได้ ก็จะเกิดลูป และอาจลามไปถึงลูปไม่สิ้นสุด
- CSS Working Group ถูกสรุปว่าแก้ปัญหานี้ด้วย การจำกัดทิศทางการไหลของข้อมูล
- ลูกหลานคิวรีข้อมูลของบรรพบุรุษได้ แต่ไม่อนุญาตให้ย้อนกลับในทิศทางตรงกันข้าม
- วิธีนี้ช่วยรักษาความมีขอบเขตจำกัดได้โดยไม่ต้องมีความหมายเชิง fixpoint
- ข้อมูลจึงแพร่ลงตามต้นไม้เท่านั้น และโครงสร้างยังไม่กลายเป็นการใส่ base fact ใหม่เพิ่มเข้าไป
- ต้นฉบับอธิบายแนวโน้มนี้ว่า CSS ขยับเข้าใกล้เอนจิน Datalog แต่จงใจไม่ไปถึงจุดนั้น
- CSSLog อยู่ฝั่งที่ยอมให้เกิดวัฏจักรและประเมินจนถึง fixpoint
- ส่วน CSS จริงหยุดไว้ที่จุดที่มันยังเป็นเอนจินเรนเดอร์ของเบราว์เซอร์ ไม่ใช่เอนจินฐานข้อมูลเชิงสัมพันธ์แบบเพิ่มพูน
ความเป็นไปได้ในอีกทิศทางหนึ่ง
- แทนที่จะใส่ความหมายแบบ Datalog ลงในเบราว์เซอร์ ก็อาจทำในทิศทางกลับกัน คือ นำไวยากรณ์แบบ CSS ไปวางบน Datalog
- ไวยากรณ์ของ Datalog อย่าง
:-, จุดปิดประโยค, ธรรมเนียมตัวพิมพ์ใหญ่เล็ก, การไม่มีประโยคกำหนดค่า อาจเป็นอุปสรรคต่อผู้ใช้ภาษาสมัยใหม่
- ไวยากรณ์ของ Datalog อย่าง
- CSS มี ไวยากรณ์ที่จัดการโครงสร้างต้นไม้โดยตรง อยู่แล้ว
- มีตัวเชื่อมแบบลูกหลาน ลูกโดยตรง และพี่น้อง จึงแสดงความสัมพันธ์พาเรนต์-ลูกได้อย่างเป็นธรรมชาติ
- ใน Datalog ทั่วไป โครงสร้างแบบนี้มักต้องถูกเข้ารหัสเป็นรูปเชิงสัมพันธ์ที่ค่อนข้างยุ่งกว่า
- เนื้อหาย้ำว่าข้อมูลจริงจำนวนมากมี โครงสร้างแบบต้นไม้
- JSON
- AST
- ระบบไฟล์
- ผังองค์กร
- XML ถูกยกเป็นตัวอย่าง
- หากมีเครื่องมือที่รวม recursion แบบ fixpoint เข้ากับไวยากรณ์สไตล์ CSS และความสัมพันธ์พาเรนต์-ลูกแบบปริยาย ก็อาจทำให้เราเขียน recursive tree queries ได้ด้วยสัญกรณ์ที่คุ้นเคยกว่า
- จากมุมมองของต้นฉบับ ดูเหมือนว่า ยังไม่มีใครสร้างเครื่องมือแบบนั้นออกมาได้อย่างจริงจัง
- และปิดท้ายว่าบางทีใครสักคนอาจสร้างอะไรทำนอง “CSSLog” ที่มีชื่อดีกว่านี้ขึ้นมาได้
เชิงอรรถ
- มีเชิงอรรถเกี่ยวกับ การทำให้องค์ประกอบ HTML ง่ายขึ้น
- ผู้เขียนคาดว่าอาจมีข้อโต้แย้งย่อยว่าของที่ CSS จัดการไม่ได้มีแค่องค์ประกอบ HTML ทั้งหมดเสมอไป แต่ในเนื้อหาหลักเลือกใช้องค์ประกอบ HTML เพื่อให้คำอธิบายง่ายขึ้น
- naive evaluation ไม่มีประสิทธิภาพเพราะคำนวณข้อเท็จจริงที่รู้แล้วซ้ำทุกครั้ง
- มีการกล่าวถึงวิธีปรับปรุงมาตรฐานคือ semi-naive evaluation
- หัวใจสำคัญคือในแต่ละขั้นจะดูเฉพาะข้อเท็จจริงที่อนุมานใหม่เท่านั้น
- ยังเสริมด้วยว่าลูปไม่สิ้นสุดเองก็ไม่ใช่เรื่องแปลกใน ภาษาแบบ Turing-complete
- ใน JavaScript ก็สามารถเขียน
while true {}ได้ - แต่บริบทที่แนบมาคือเราไม่อยากให้ระบบเรนเดอร์ของเบราว์เซอร์ค้างตลอดไปเพราะความสับสนทางตรรกะของเว็บไซต์
- ใน JavaScript ก็สามารถเขียน
- เชิงอรรถยังกล่าวถึง ทางอ้อมด้วย custom property inheritance ของ CSS
[data-theme="dark"] { --effective-theme: dark; }[data-theme="light"] { --effective-theme: light; }@container style(--effective-theme: dark) { :focus { outline-color: white; } }- วิธีนี้ใช้ได้ค่อนข้างดีในกรณีเฉพาะนี้ แต่ inheritance ไม่ได้เท่ากับ transitive closure ที่แท้จริง
- ในปัญหาที่ซับซ้อนกว่านี้ ซึ่งต้องการ transitive closure ตามสายคุณสมบัติที่ไม่ใช่พาเรนต์-ลูก วิธีนี้ก็จะพังลง
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
CSS selector เขียนง่ายกว่า XPath มาก
ไม่นานมานี้ยังมีการพูดถึงด้วยว่า DOM API ใหม่ของ PHP ทำให้จัดการ HTML และ CSS selector ได้แบบเนทีฟอย่างง่ายมาก แต่ก่อนต้องแปลง CSS ไปเป็น XPath
[1] https://speakerdeck.com/keyvan/parsing-html-with-php-8-dot-4...
แต่น่าเสียดายที่มันพัฒนามาโดยเน้นการจัดสไตล์บนเบราว์เซอร์ เลยไม่มีความสามารถอย่างการ เลือกตามเนื้อหาข้อความ แบบ XPath
เท่าที่รู้เคยมีข้อเสนอมาก่อน แต่เข้า spec ไม่ได้เพราะอาจมีปัญหาด้านประสิทธิภาพในบริบทการเรนเดอร์ของเบราว์เซอร์
ตอนทำเอเจนต์สำหรับแก้ไขเอกสาร ผมแสดงเอกสารเป็น HTML แล้วให้ LLM ระบุแค่ CSS selector เพื่อดึงชิ้นส่วนที่ต้องการเข้ามาเป็นคอนเท็กซ์ ซึ่งได้ผลดีแทบจะเหมือนเวทมนตร์
คนสามารถใช้วิธีที่คุ้นเคยได้เหมือนเดิม
น่าจะดีถ้ามีชื่อที่ใช้เรียกแยกระหว่าง ไวยากรณ์ CSS กับระบบทั้งหมดของกฎ ฟังก์ชัน หน่วย ฯลฯ ที่ CSSWG นิยามไว้
ฝั่งนี้ดูมีศักยภาพพอตัว แต่ถ้าจะคุยหรือค้นคว้าเคสการใช้งานอื่น ๆ สุดท้ายก็ดูเหมือนต้องไปคุ้ยโค้ดบน GitHub ที่มี CSS parser อยู่ เพื่อดูว่าคนกำลังสร้างของประหลาดอะไรบ้าง
ผมเองก็กำลังลองจับของประหลาดคล้าย template engine ที่ผสมภาษามาร์กอัปแบบเบา ๆ ที่อิง node, CSS selector สำหรับบอกว่าอะไรจะเข้าไปในเทมเพลต, และไวยากรณ์คล้าย CSS สำหรับควบคุมว่าชิ้นส่วนเหล่านี้จะประกอบกันอย่างไร
https://www.w3.org/TR/selectors-3/
และสเปก DOM ก็อ้างอิงสิ่งนี้
https://dom.spec.whatwg.org/#selectors
ดังนั้นคำเรียกรวมว่า CSS selector ก็ถูกต้องอยู่แล้ว หรือจะเรียกแค่ selector ก็ได้
ชื่อ DOM selector อาจดูสะอาดตากว่า แต่ถ้าคิดถึง selector ที่ใช้ใน CSS แบบสแตติกหรือใน DOM engine อื่นนอก JS engine เช่น XML parser หรือ PHP DOM API ก็อาจยิ่งชวนสับสน
อีกอย่างยังมี selector พิเศษอย่าง
:hoverหรือ::target-textที่ผูกตรงกับการเรนเดอร์และการนำทางของเบราว์เซอร์แต่ถ้าจะตั้งชื่อแยกให้กับ ส่วนย่อยขั้นต่ำของไวยากรณ์คิวรี ที่ผูกกับเบราว์เซอร์หรือ CSS น้อยกว่า ก็น่าจะมีประโยชน์
นึกถึง https://github.com/braposo/graphql-css ที่เคยเห็นในงานคอนเฟอเรนซ์เมื่อก่อน
มันเป็นโปรเจ็กต์เล่น ๆ แต่ผมชอบที่มันแสดงให้เห็นได้ดีว่า การย้ายรูปแบบหนึ่งไปปลูกในอีกบริบทแล้วนำกลับมาใช้ใหม่ สามารถเปิดทางให้เกิดอะไรที่คาดไม่ถึงได้
ผมก็กำลังลองหยิบแพตเทิร์นจากต่างบริบทมาใช้ในแนวนี้พอดี
ส่วนใหญ่คงไปไม่ไกลมาก แต่ในแง่ความเป็นแฮ็กเกอร์มันน่าสนใจไม่น้อย
pyastgrep อย่างที่เห็นใน https://pyastgrep.readthedocs.io/en/latest/ สามารถใช้ CSS selector เพื่อคิวรีไวยากรณ์ของ Python ได้
ค่าเริ่มต้นคือ XPath เช่น
pyastgrep --css 'Call > func > Name#main'มันเกือบตรงกับทิศทางที่ผมอยากชี้เลย
ผมยังไม่ค่อยเข้าใจว่ามันแก้สถานการณ์แบบไหน
ตอนนี้ก็เปลี่ยนพาเรนต์แบบมีเงื่อนไขตามลูกได้อยู่แล้ว เช่น
preมี padding ปกติ 16px แต่ถ้ามีลูกโดยตรงเป็นcodeก็ทำให้เป็น 0 ได้ด้วย&:has(> code)ข้อสรุปเลยไม่ใช่แนวว่า "ต้องแก้ข้อจำกัดของ CSS สมัยใหม่" แต่ใกล้เคียงกับว่า ถ้าเอา ไวยากรณ์คล้าย CSS ไปวางบน ระบบคล้าย Datalog มันอาจทำให้งานกับข้อมูลแบบต้นไม้ดูคุ้นมือสำหรับวิศวกรมากขึ้นไหม
พูดอีกแบบคือเป็นเรื่องของการเพิ่มองค์ประกอบลูกหรือแอตทริบิวต์ใหม่เข้าไปใน DOM
LLM ตอนนี้จริง ๆ แล้วไม่ได้เก่งเรื่อง CSS มากนัก เลยยิ่งอยากลองดูว่าถ้าทำแบบนี้แล้ว LLM จะให้เหตุผลได้ง่ายขึ้นไหม
นึกการใช้งานจริงไม่ค่อยออก แต่ก็ยังเท่อยู่ดี
อืม... ฟังดูเหมือนนี่คือ JQ เฉย ๆ หรือเปล่า
ผมชอบ CSS ในระดับหนึ่ง แต่ไม่ชอบที่มันมี ความซับซ้อนค่อย ๆ เพิ่มพอกพูน มากขึ้นเรื่อย ๆ
ผมเข้าใจเหตุผลที่ว่าภาษาโปรแกรมย่อมทรงพลังกว่าภาษาที่ไม่ใช่ภาษาโปรแกรม แต่แทนที่จะทำให้ HTML·CSS·JavaScript ซับซ้อนขึ้นไปเรื่อย ๆ ผมกลับรู้สึกว่าน่าจะดีกว่าถ้ามีอะไรสักอย่างมาแทนทั้งก้อนนั้นไปเลย
องค์ประกอบใหม่ ๆ ใน HTML5 ส่วนใหญ่ผมก็ยังไม่ค่อยเข้าใจว่าจำเป็นไปเพื่ออะไร เลยแทบไม่ใช้ สุดท้ายก็เริ่มคิดว่าคอนเทนเนอร์จำนวนมากก็เป็นแค่
divที่มี ID เฉพาะเท่านั้น และถึงขั้นเคยอยากให้มี alias สำหรับ ID แบบนั้นไว้ใช้กับการนำทางhrefภายในหน้าอะไรอย่าง
[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; } }มันแปลกที่มาตรฐานซึ่งเคยทำงานได้ดี กลับดูเหมือนพังลงเรื่อย ๆ ตามเวลา
ตัวอย่างเช่น
[data-theme="dark"] [data-theme="light"] :focus { outline-color: black; }ถ้าคลี่เป็น pseudocode ภาษาอังกฤษ ก็จะใกล้เคียงกับว่า มี X ที่data-theme="dark"และมีลูก Y ของมันที่data-theme="light"และอยู่ในสถานะโฟกัส ก็ให้outline-colorของ Y เป็น 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)ดูเป็น syntactic sugar คล้าย UFCS แบบX.attr == valได้ด้วยถ้าอยากให้ดูเป็นสาย ALGOL มากขึ้น ก็อาจเป็น
forall Y { Y.outline_color := black if Y.data_theme == "dark" and Y.focused and Y.parent.data_theme == "light" }ตรงนี้มีการประกาศ Y อย่างชัดเจนและทำให้ join หนึ่งตัวเป็นนัย เพื่อให้ดูเหมือนการเขียนโปรแกรมทั่วไปมากขึ้น แต่ในความเป็นจริงก็คือ Datalog engine จะคอยรันลูปแบบนี้อย่างมีประสิทธิภาพทุกครั้งที่ dependency เปลี่ยน