- ไวยากรณ์ของ JavaScript ซับซ้อนได้ง่ายจากวงเล็บที่ซ้อนกันและคอลแบ็ก และยังเกิดอาการบวมจากการดึงไลบรารีจำนวนมากมาใช้แม้เป็น UI เล็ก ๆ
- WebAssembly เปิดทางให้รันภาษาอื่นในเบราว์เซอร์ได้ แต่ก็มีต้นทุนสูงในการเชื่อมต่อแบบอะซิงโครนัสกับ JavaScript event loop อย่างกรณีของ Pyodide
- ทรัพยากรของเบราว์เซอร์และหน่วยความจำของ WebAssembly มีจำกัด จึงควรใช้แนวทางแบบ เจรจา มากกว่าพยายามแทนที่ JavaScript
- LispE บรรจุฟังก์ชันมากกว่า 450 ตัวไว้ในไบนารี WASM ขนาด 3.3MB เพียงไฟล์เดียว พร้อมความสามารถด้านสตริง การคำนวณ เมทริกซ์ และเรกิวลาร์เอกซ์เพรสชัน
- ใช้
evaljs และ asyncjs เพื่อเรียกฟังก์ชัน JavaScript และใช้งาน DOM ได้ พร้อมลด โค้ดบวม ด้วยไบนารีเดี่ยวที่ตรวจสอบได้ แทนการพึ่งไลบรารีภายนอกจำนวนมาก
โค้ดบวมของ JavaScript และข้อจำกัดของเบราว์เซอร์
- ไวยากรณ์ของ JavaScript มีทั้งวงเล็บ กล่องปีกกา และวงเล็บเหลี่ยมซ้อนกัน ต้องคอยจับคู่ลำดับการปิดให้ถูก จึงทำให้โค้ดซับซ้อนได้ง่าย
- มีตัวอย่างการใส่ค่าลงในอ็อบเจ็กต์หลายตัวภายในคอลแบ็กของ
forEach พร้อมอัปเดตแคชแบบมีเงื่อนไข
newNames.forEach((name, i) => {
allAgentContents[name] = contents[i];
agentModes[name] = modes[i];
if (compiled[i]) agentCompiledCache[name] = compiled[i];
agentViewingCompiled[name] = viewing[i];
});
- สำหรับงานที่ภาษาโปรแกรมทั่วไปมักมีมาให้ JavaScript กลับต้องดาวน์โหลด ไลบรารี จำนวนมาก
- C หรือ C++ ก็ต้องมี
include เพื่อทำงานเช่นกัน แต่ไลบรารี JavaScript หลายครั้งไม่ชัดเจนว่าใครเป็นคนพัฒนาและใครเป็นผู้ดูแลรักษา
- บางเพจดูราวกับต้องโหลด “ครึ่งหนึ่งของอินเทอร์เน็ต” เพียงเพื่อแสดงคำว่า hello ในหน้าต่างเล็ก ๆ
- อินเทอร์เน็ตพึ่งพา JavaScript และถึงจะซ่อนมันไว้ด้วย TypeScript มันก็ยังเป็นด่านบังคับทุกครั้งที่เปิดหน้าเว็บในเบราว์เซอร์
- เคยมีความฝันว่าเบราว์เซอร์จะกลายเป็นระบบปฏิบัติการสูงสุดจนไม่จำเป็นต้องมี Windows หรือ Mac OS และ JavaScript ก็ถูกเลือกให้เป็นภาษาที่ใช้สร้างความฝันนั้น
ความสัมพันธ์ระหว่าง WebAssembly และ JavaScript
- WebAssembly ใกล้เคียงกับเครื่องเสมือนที่มีภาษาเครื่องของตัวเอง จึงเปิดโอกาสให้เขียนโปรแกรมในเบราว์เซอร์ได้ด้วยวิธีอื่น
- Pyodide เป็นผลงานวิศวกรรมที่น่าประทับใจในการรัน Python บนเบราว์เซอร์ แต่ก็เผยให้เห็นต้นทุนของการทำงานภายในขอบเขตของ JavaScript
- โลกอะซิงโครนัสสองฝั่งคือ
asyncio ของ Python และ JavaScript event loop ต้องคุยกันข้ามฝั่ง
- สะพานระหว่างสองโลกนี้เปราะบาง และต้องจำให้ได้ว่า
await แต่ละตัวอยู่ในโลกไหน
- ทรัพยากรของเบราว์เซอร์มีจำกัด จึงต้องกลับไปคิดแบบยุคแรกของวิทยาการคอมพิวเตอร์ ที่พิจารณาทุกคำสั่งและทุกโครงสร้างในระดับบิต
- มีการยกเกณฑ์อ้างอิงว่าผู้เขียนเริ่มเขียนโปรแกรมบนคอมพิวเตอร์ที่เหลือหน่วยความจำอยู่เพียง 15772 bytes ในปี 1981
- แม้หน่วยความจำของเบราว์เซอร์สมัยใหม่จะไม่ได้จำกัดเท่าคอมพิวเตอร์เครื่องแรกนั้น แต่ WebAssembly ก็ยังไม่สามารถครอบครองหน่วยความจำได้อย่างอิสระแบบโปรแกรมทั่วไป และต้องขอสิทธิ์ล่วงหน้าในรูปแบบที่จำกัด
- ท่าทีสำคัญคือไม่ใช่การต่อสู้กับ JavaScript แต่เป็นการ เจรจา กับมัน
ทางเลือกที่ LispE เสนอ
- LispE มี ฟังก์ชันมากกว่า 450 ตัว อยู่ภายในไบนารีเดียว
- ไบนารี WASM มีขนาด 3.3 MB
- รวมความสามารถด้านสตริง การคำนวณ เมทริกซ์ และการประมวลผลเรกิวลาร์เอกซ์เพรสชันไว้ในที่เดียว
- ไบนารี WASM อยู่ที่ binaries/wasm
- แม้เป็นอินเทอร์พรีเตอร์ขนาดเล็ก แต่ก็รองรับค่าที่ส่งกลับได้ทั้งสตริง,
Float64Array, จำนวนเต็ม, จำนวนจริง และอาร์เรย์ของสตริง
- LispE มีพื้นฐานแบบ Lisp จึงมีโครงสร้างที่ AST ยังมีชีวิตอยู่
- หากไวยากรณ์แบบ Lisp ไม่เหมาะ ก็สามารถใช้ไวยากรณ์แบบทรานส์ไพล์สำหรับภาษาที่แทบแยกไม่ออกจาก Python หรือ Basic ได้
- LispE ไม่ได้สู้กับ JavaScript แต่ร่วมมือกับมันด้วยการใช้ฟังก์ชัน JavaScript และความสามารถของ DOM
การเรียก JavaScript: evaljs และ asyncjs
- หากต้องการรันโค้ด JavaScript จากภายใน LispE สามารถใช้
evaljs และ asyncjs ได้
evaljs ใช้รันโค้ด JavaScript แล้วรับค่ากลับ
asyncjs ใช้เชื่อมต่อกับฟังก์ชัน JavaScript ที่ผู้ใช้กำหนดไว้ในหน้าเว็บและคอลแบ็กแบบอะซิงโครนัส
(setq a (evaljs "10 + 20 + 30")) ; execute some JS code
; call_llm is a user-defined JS function in the page
(asyncjs `call_llm("Implement a piece of code in Python to sort strings");` 'mycallback)
(defun mycallback(theresult) ...)
asyncjs ทำงานในลักษณะของ Promise ที่จะกลับมาเมื่อทำงานเสร็จ
ลดโค้ดบวม
- คำอ้างอิงของ Wirth ในปี 1995 นำไปสู่ข้อสรุปว่า คอมพิวเตอร์ที่เร็วขึ้นและโมเดลที่ใหญ่ขึ้นไม่ได้แก้ปัญหา และเราควรรักษาโค้ดให้เพรียวไว้
- LispE เปิดให้ใช้งาน 450 ฟังก์ชัน บนเบราว์เซอร์ผ่านไบนารีเดี่ยวที่ตรวจสอบได้
- หากจะแยกทำแต่ละความสามารถเอง ก็ต้องโหลดไลบรารีอย่าง
mathjs สำหรับคำนวณเชิงตัวเลข, lodash สำหรับคอลเลกชัน, voca สำหรับจัดการสตริง, simple-statistics สำหรับการแจกแจงทางสถิติ
- วิธีนี้อาจขยายกลายเป็นโค้ดขนาดหลายร้อย MB ที่มาจากผู้เขียนต่างกัน มีบั๊กต่างกัน และมีรอบการบำรุงรักษาต่างกัน
- LispE ให้ความสามารถเหล่านี้ผ่านโค้ดชุดเดียวที่ได้รับการดูแลรักษา และเป็นโอเพนซอร์สจึงตรวจสอบโค้ดทั้งหมดได้
- มองว่า Garbage Collector จะไม่ทำลายประสิทธิภาพของโค้ดในจังหวะที่เลวร้ายที่สุด
- สื่อสารกับ JavaScript อย่างโปร่งใสผ่านการเรียก API แบบง่าย และสามารถส่งคืนโครงสร้างที่กำหนดไว้ล่วงหน้าได้
// floats here is a Float64Array
const floats = callEvalLispEToFloats(0, `(normal_distribution 100)`);
2 ความคิดเห็น
ตอนแรกผมรู้สึกว่าบทความมันโผล่มาแบบงง ๆ เลยสงสัยแหล่งที่มา แต่ดันเป็น Naver ซะนี่ เดือดเลย
แต่ดูแล้วกระแสตอบรับก็ยังไม่ค่อยดีอยู่ดี... พูดตรง ๆ คือการที่ JavaScript ถูกปั่นจนถึงขั้นจะอัป WASM ขนาด 3.3MB ขึ้นมาเพื่อใช้ Lisp มันก็เป็นโอเวอร์เอนจิเนียริงที่เข้าใจได้ไม่ง่ายจริง ๆ 555
มีนักพัฒนาโปรเจกต์ชื่อ Claudius มาคอมเมนต์เรื่องที่ใช้บัญชี Naver โดยบอกว่าตัวเองทำงานอยู่ที่ Naver Labs Europe และ Naver อนุมัติให้โพสต์ในฐานะโปรเจกต์โอเพนซอร์สได้
ดูเหมือนจะไม่ได้เกี่ยวกับ Naver มากนัก น่าจะแค่เป็นคนที่รัก Lisp จริง ๆ มากกว่า...
ความเห็นจาก Lobste.rs
พอบอกว่าจะทำให้ JavaScript บวมลดลง แต่กลับเพิ่ม ก้อน WASM ขนาด 3.3MB ที่ตรวจสอบภายในได้ยาก แล้วให้เขียนแอปด้วย Lisp เหรอ
แค่ JavaScript ล้วน ๆ และไม่มี dependency เพิ่มเลย ก็ยังสร้างอะไรได้เยอะมากก่อนจะเข้าใกล้ขนาดแค่ 1 ใน 10 ของนั้นเสียอีก
แม้ผมจะไม่ได้เห็นด้วยกับวิธีใช้งานนี้ แต่ก็เป็น โปรเจกต์แพชชัน Lisp ที่น่าสนใจและแปลกแบบที่เดี๋ยวนี้หาได้ยาก และชอบตรงที่ทั้งเอกสารมีสำนวนเฉพาะตัวเหมือนเขียนด้วยมือโดยคนจริง ๆ
อะไรที่ควรเพิ่มเข้าไปใหม่ใน standard library เราเปิดรับข้อเสนออยู่เสมอ
ของที่เพิ่งเพิ่มไปไม่นานนี้มี set union/intersection,
sum,base64เป็นต้นแต่แทบไม่เคยได้ยินคำขอฟังก์ชันแคลคูลัสเลย ส่วนฝั่งสตริงที่มีคนขอเรื่อย ๆ ก็ประมาณ
.reverseหรือ.titleCase.reverseนั้นนอกจากโจทย์ของเล่นก็ไม่ค่อยมี use case ที่น่าโน้มน้าวนัก ส่วน.titleCaseทำได้ แต่ต้องใช้ข้อมูลด้านการ internationalization เลยยังคุยกันอยู่แถม ทั้งสองอย่างก็ไม่มีอยู่ในไลบรารีนี้ด้วย
หลายคนรู้สึกว่าต่อให้เสนอไปก็คงต้องรอ 3 ปีกว่าจะถูกนำไปใช้จริง เลยไม่คุ้มจะลอง และแม้จะไม่ชอบโฟลเดอร์
utils/แต่ก็คิดว่าอย่างน้อยทำเองได้ทันทีประเด็นของผมใกล้กับเรื่อง โมเดลการแจกจ่าย มากกว่าจะเป็นเรื่อง JavaScript ขาดฟังก์ชัน
ต่อให้ standard library แน่นแค่ไหน แอปทั่วไปก็ยังขน npm dependency ทางอ้อมออกไปเป็นระดับเมกะไบต์อยู่ดี
LispE-as-WASM คือ 3.3MB, มีฟังก์ชันที่มีเอกสารราว 450 ตัว, มีแกนหลัก C++, และเป็นซอร์สเปิดบน GitHub เป็นการทดลองเพื่อดูว่า runtime ที่ตรวจสอบได้อาจมีหน้าตาแบบไหน
ผมทำ agent harness บนมันจริง ๆ แล้ว และกำลังรันกฎของ LispE แบบสด ๆ ในเบราว์เซอร์
ส่วน JavaScript event loop นั้นผมชอบมาก
เวลาใครบอกว่า ไวยากรณ์ มันบวม ผมจะเริ่มระแวงทันที
ไวยากรณ์ที่น้อยที่สุดอาจอ่านยากกว่าไวยากรณ์ที่ใช้สัญลักษณ์ต่างกันเพื่อแบ่งแยกทางสายตาอย่างมาก
สิ่งที่ LispE เสนอมาเป็นทางเลือกดูประมาณนี้:
... !?
https://github.com/naver/lispe/wiki/5.3-A-la-APL
จุดเริ่มต้นของโปรเจกต์นี้ดูแปลก ๆ ไปหน่อย และฝั่ง https://github.com/naver/lispe/wiki/1.-Introduction น่าจะดีกว่า
มันคือ โปรเจกต์ Lisp แบบเนทีฟ ที่ปล่อยให้ใช้กับ WASM ได้ด้วย และมีมุมสนุก ๆ ที่น่าจะถูกใจพวกชอบคลั่งไคล้ภาษาโปรแกรม
ความประทับใจแรกคือมันเหมือนตัวอย่าง JavaScript ที่ฝืนมาก
ความเกลียด JavaScript จำนวนมากน่าจะมาจากยุคแรก ๆ ที่เบราว์เซอร์เข้ากันไม่ได้, DOM API ที่ทรมาน, และกับดักทางไวยากรณ์ ซึ่งทุกวันนี้แทบไม่กระทบงานประจำของผมแล้ว
JavaScript ยุคใหม่ โดยเฉพาะเมื่อใช้ TypeScript กับกฎ lint ที่สมเหตุสมผล ก็ถือว่าใช้เป็นภาษาได้ดีพอแล้ว
ยังมีปัญหาอย่าง CJS เทียบกับ ESM, ความเสี่ยงด้านซัพพลายเชน, และความผันผวนของ ecosystem อยู่ แต่ส่วนใหญ่ไม่ใช่ปัญหาของตัวภาษาเอง
ประสบการณ์แบบที่คนรำคาญกับการจัดวงเล็บให้สมดุลมากจนต้องทำส่วนขยาย editor ขึ้นมาเองนี่แหละที่ยอดเยี่ยมงั้นเหรอ
ถ้าต้องการการเขียนโปรแกรมสไตล์นี้ คือ ความหนาแน่นของไวยากรณ์ต่ำและแนวทางแบบ functional ผมว่ากลับทิศไปใช้ภาษาแบบ concatenative จะดีกว่า
สงสัยจริงว่า interpreter ของ Lisp พอคอมไพล์แล้วทำไมถึงใหญ่ได้ถึง 3.3MB
นี่เรียกว่าทำให้บวมน้อยลงเหรอ?
ถึงอย่างนั้นก็เห็นด้วยว่าการแจกจ่าย standard library ก้อนใหญ่พร้อมโค้ดที่ไม่ได้ใช้นั้นห่างไกลจากคำว่าทำให้บวมน้อยลงมาก
ช็อก! สยอง!
ต้องปิดวงเล็บตามลำดับให้ถูกต้อง ช่างน่าตกใจ
แม้แต่คนรัก Lisp ที่รังเกียจความยืดยาวของภาษาอย่าง JavaScript พอเจอภาษาตระกูล APL ก็มักจะถอย แล้วเริ่มถกกันว่า ความชัดเจน กับ ความอ่านง่าย สำคัญกว่าการทำให้สั้นที่สุดมาก
ผู้เขียน LispE ดูเหมือนจะชอบตัวดำเนินการพื้นฐานของ APL อยู่พอสมควร แต่ก็ยากจะเข้าใจว่าทำไมหลังจากได้เห็นภาษาที่มีสัญลักษณ์กระชับขนาดนั้นแล้ว ถึงยังชอบ Conway's Life เวอร์ชันที่ยาวกว่า โปร่งกว่า และซ้อน s-expression ลึกกว่าเวอร์ชัน APL, J, K หรือแม้แต่ Q