ClojureScript นำ Async/Await มาใช้
(clojurescript.org)- ClojureScript 1.12.145 มีการเปลี่ยนคอมไพเลอร์ให้คอมไพล์ฟังก์ชันที่ติด hint
^:asyncออกมาเป็น JavaScriptasync function - สามารถเขียนฟังก์ชัน ClojureScript ที่รอค่า Promise ด้วย
awaitได้ ทำให้การทำงานร่วมกับ JavaScript ดีขึ้น - ยังสามารถใช้
^:asyncกับการทดสอบได้ และตรวจสอบผลลัพธ์ของการเรียกฟังก์ชันแบบอะซิงก์ด้วยawaitได้ - ในแบบสำรวจ Clojure ล่าสุด การรองรับ async functions เป็นคำขอปรับปรุง ClojureScript ด้านการทำงานร่วมกับ JavaScript ที่มีสัดส่วนสูงที่สุด
- สำหรับกรณีทั่วไปที่ต้องใช้งาน Browser API รุ่นใหม่และไลบรารียอดนิยม ความจำเป็นในการเพิ่ม dependency เพิ่มเติม จะลดลง และสามารถดูรายการการเปลี่ยนแปลงทั้งหมดได้ที่ หัวข้อ 1.12.145 ใน ClojureScript changelog
การใช้ ^:async และ await
- ClojureScript 1.12.145 มีการเปลี่ยนคอมไพเลอร์ให้คอมไพล์ฟังก์ชันที่ติด hint
^:asyncออกมาเป็น JavaScript async function - เมื่อ ClojureScript กำหนดเป้าหมายไปที่ ECMAScript 2016 ก็ทำให้สามารถเลือกจุดที่จะปรับปรุงการทำงานร่วมกับ JavaScript ได้อย่างรอบคอบ
- สามารถเขียนฟังก์ชัน ClojureScript ที่รอค่า
Promiseด้วยawaitได้(refer-global :only '[Promise]) (defn ^:async foo [n] (let [x (await (Promise/resolve 10)) y (let [y (await (Promise/resolve 20))] (inc y)) ;; not async f (fn [] 20)] (+ n x y (f)))) - ยังสามารถใช้
^:asyncกับการทดสอบได้ และตรวจสอบผลลัพธ์ของการเรียกฟังก์ชันแบบอะซิงก์ด้วยawaitได้(deftest ^:async defn-test (try (let [v (await (foo 10))] (is (= 61 v))) (let [v (await (apply foo [10]))] (is (= 61 v))) (catch :default _ (is false))))
ที่มาและรายการเปลี่ยนแปลง
- ในแบบสำรวจ Clojure ล่าสุด การรองรับ async functions เป็นคำขอปรับปรุง ClojureScript ด้านการทำงานร่วมกับ JavaScript ที่มีสัดส่วนสูงที่สุด
- การปรับปรุงครั้งนี้ช่วยลดความจำเป็นในการเพิ่ม dependency เพิ่มเติมในกรณีทั่วไปที่ต้องใช้งาน Browser API รุ่นใหม่และไลบรารียอดนิยม
- ดูรายการการแก้ไข การเปลี่ยนแปลง และการปรับปรุงทั้งหมดได้ที่ หัวข้อ 1.12.145 ใน ClojureScript changelog
- ClojureScript 1.12.145 มีผลงานจากสมาชิกชุมชน Michiel Borkent
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
borkdude เป็นคนโพสต์เธรดนี้ และเห็นว่าเขาถูกระบุเป็นผู้มีส่วนร่วมในรีลีสนี้ด้วย
เหตุผลคัดค้านการรองรับ async/await มานานส่วนใหญ่มีอยู่สองข้อ: ต้องแก้ไขเชิงลึกทั่วทั้งคอมไพเลอร์ CLJS และมีแมโครจากไลบรารีอย่าง Promesa ที่ให้ความสะดวกคล้ายกันอยู่แล้ว
นอกจากนี้ก็มีคนบอกว่าใช้ core.async ก็พอ หรือภาษาแบบเน้น expression ไม่ค่อยเข้ากับ async/await แต่สิ่งเหล่านั้นใกล้เคียงกับความเห็นส่วนบุคคลมากกว่าจะเป็นเหตุผลหลักที่ถูกพูดซ้ำๆ ในฟอรัม
ใน Clojurians Slack นั้น borkdude เคยบอกว่าเขาไม่ได้มั่นใจว่าการเพิ่มการรองรับเป็นเรื่องที่เป็นไปไม่ได้ และสุดท้ายก็ดูเหมือนเขาจะใช้เวลาลงมือทำจนสำเร็จ ขอบคุณมากจริงๆ
เรื่องที่น่าสนใจคือ ClojureScript รองรับพาราไดม์ asynchronous ผ่านไลบรารี core.async มานานมากแล้ว ก่อนที่ JavaScript จะมี async/await เสียอีก
ไม่ได้จะลดคุณค่าของรีลีสนี้เลย แค่อยากบอกว่าการเพิ่มไลบรารีเข้า dependency เพียงตัวเดียวแล้วได้ใช้ความสามารถภาษาใหม่ที่ยังไม่มีในภาษาโฮสต์นั้นเท่มาก Clojure เจ๋งจริง
น่าจะได้รู้เรื่องนี้จากการบรรยายของ David Nolen
หลังจากนั้นผมก็ค่อยๆ ขยับไปทางใช้ JavaScript ในฝั่งฟรอนต์เอนด์ให้น้อยที่สุด และ SSE เป็นแบบทางเดียวซึ่งสวยงามในแง่นั้น ดีใจที่เดี๋ยวนี้นักพัฒนาจากหลายภาษาเริ่มสนใจ SSE กันมากขึ้น
งานพูดล่าสุดของ David Nolen ชื่อ “A ClojureScript Survival Kit” ก็ดีมาก: https://youtu.be/BeE00vGC36E
ต่อให้อธิบายความขอบคุณที่มีต่อสิ่งที่ David “Swannodette” Nolen ทำมาตั้งแต่ยุคแรกของ ClojureScript และ core.async ก็ยังไม่พอ สิ่งที่น่าทึ่งเป็นพิเศษในทอล์กนี้คือ เขาดูตื่นเต้นจริงๆ กับแนวทางที่ละ ClojureScript ไปใช้ Clojure ฝั่งเซิร์ฟเวอร์ล้วนๆ กับ server-sent events และ JavaScript เพียงเล็กน้อย
เดโมจริงเริ่มราวๆ นาที 26:30 หลังจากแสดงการใช้ทรัพยากรของเว็บแอปที่รันบนไคลเอนต์ เขาก็โชว์เว็บแอปเดียวกันที่รันบนเซิร์ฟเวอร์และ push แบบทางเดียวมายังไคลเอนต์ด้วย SSE ซึ่งการใช้ทรัพยากรแทบจะเป็นศูนย์ ดูทรงพลังมาก
อาจไม่เหมาะกับทุกกรณี แต่การใช้ไลบรารีสำหรับแก้ไข DOM เพียงน้อยนิดทำให้เข้าใจเว็บแอปและสถานะของมันได้ง่ายขึ้นมาก แต่ก่อนผมต้องเปิดทั้ง REPL ของ Clojure และ REPL ของ ClojureScript พร้อมกัน และต้องรับมือกับทราฟฟิกสองทางกับสถานะที่ทำซ้ำได้ยากเยอะมาก ตอนนี้เร็วกว่าและทำซ้ำได้ง่ายกว่ามาก
ผลลัพธ์ JavaScript มีขนาดใหญ่ขึ้น ไม่มีโมเดลข้อผิดพลาดในตัว และเมื่อมีปัญหาก็จะถูกแปลงเป็นโค้ด state machine ที่อ่านและดีบักยาก
แถมแมโคร
goก็แปลงโค้ดนอก S-expression ของตัวเองไม่ได้ จึงผลักดันให้ฟังก์ชันโตเกินความจำเป็นอย่างที่คนจาก Cognitect เคยพูดไว้ว่า “core.async คือเรื่องไร้สาระที่งดงาม”
ค่อนข้างแปลกใจที่พักหลังนี้เห็น Clojure/ClojureScript บนโซเชียลบ่อยขึ้นแบบกะทันหัน
ผมเคยใช้มันในงานอยู่หลายปีราวปี 2012 แต่ก็ย้ายออกจาก JVM ไปหาภาษาฟังก์ชันที่มี type เหมือนอีกหลายคน
ความสนใจที่เพิ่มขึ้นตอนนี้เป็นเพราะ agentic coding หรือเปล่า? เพราะไม่มี type checking และมี syntax error แบบประหลาดๆ หรือคำสงวนให้น้อยกว่า เลยไล่อ่านโค้ดได้เร็วขึ้น? หรือว่าการกลับมาของ S-expression กำลังจะมาถึง
codebase Clojure จริงจังที่ผมรู้จักมักลงทุนกับ test suite มาก ดังนั้นถ้าเพิ่มทักษะให้ AI รู้จักใช้ test suite ให้มีประสิทธิภาพ มันก็น่าจะวิ่งได้ดีทีเดียว
เพื่อนร่วมงานบางคนก็ปล่อยให้ agent โต้ตอบกับ REPL เลย และบอกว่าเร็วขึ้นเพราะไม่ต้องเสียค่าเริ่มต้นทุกครั้ง ผมขี้เกียจเกินกว่าจะทำถึงขั้นนั้น แต่ตอนนี้ก็เร็วพออยู่แล้ว
Clojure มีส่วนที่เกะกะน้อยมาก ทุกอย่างเป็นจริงหมด ยกเว้น
falseและnil, ไม่มีตารางลำดับความสำคัญของ operator และตัวภาษาแกนกลางก็รองรับโครงสร้างข้อมูลแบบ immutable และ persistent เป็นค่าเริ่มต้นทุกอย่างเป็น expression ไม่ใช่โครงสร้างที่ปนกันระหว่าง operator กับ expression และ
map,reduce,filterก็มีมาให้ในตัวและใช้กันเป็นปกติในโค้ดทั่วไปโค้ด Clojure ที่เขียนไว้เมื่อ 10 ปีก่อนก็น่าจะยังรันได้ในวันนี้ และทั้ง ecosystem กับผู้ออกแบบภาษาก็มองการทำโค้ดเก่าพังเป็นเรื่องต้องห้าม
จากภาษาที่เคยใช้มา มันเป็นภาษาที่ให้อิสระในการถ่ายทอดความคิดมากที่สุดและปวดหัวน้อยที่สุด Flowstorm ซึ่งเป็นเหมือน reverse debugger โดยพฤตินัย ก็เป็นเครื่องมือในฝันสำหรับการเขียนโปรแกรม
ถ้าอยากเขียนโค้ดแบบสบายใจ มันเป็นภาษาที่ดีมาก ในทางกลับกัน ผู้ใช้ส่วนใหญ่กลับมองสิ่งนี้เป็นเรื่องปกติ เลยไม่ค่อยพูดถึงกันมาก
ในบรรดาโปรแกรมเมอร์ที่ใช้ Clojure เชิงพาณิชย์ ก็มีหลายคนที่ไม่ได้เข้าใจภาษาอย่างลึกซึ้งและเลยไม่ได้มีความสุขนัก มักเป็นคนที่ไม่ได้เลือกมันเองหรือยังไม่พร้อม และผมคิดว่าก่อนจะใช้ Clojure คุณควรผ่านความไม่ชอบในภาษาอื่นๆ มาสัก 10 ปี
วิดีโอเกี่ยวกับซอฟต์แวร์ของ Rich Hickey ผู้สร้าง Clojure นั้นดังและมีอิทธิพลก็จริง แต่ไม่ได้แปลว่าเพื่อนร่วมงานของคุณจะเคยดูหรือสนใจมัน
การจัดการ codebase Python ขนาดใหญ่ที่ไม่มี type ด้วย AI นั้นเหนื่อยมาก ส่วนที่ไม่มี test ครอบอยู่ก็ต้องคอยเช็กอย่างน่าเบื่อว่ามันยังไม่พัง
ยิ่งระบบ type แข็งแรงยิ่งดี และเพราะโมเดล AI เรียนจากโค้ด ภาษาที่เป็นที่นิยมกว่าก็น่าจะให้ผลลัพธ์ดีกว่า ClojureScript นั้นดี แต่ไม่ใช่ภาษากระแสหลัก ดังนั้นผมคาดว่า AI จะทำงานกับมันได้แย่กว่า JavaScript
สุดท้ายถ้าคิดถึง AI เป็นหลัก ก็ควรเลือกภาษาที่มี type หรืออย่างน้อยภาษาที่เป็น dynamic แต่มี type hint
นี่เป็นเรื่องใหญ่จริงๆ วงการ Clojure ไม่ได้รู้สึกตื่นเต้นแบบนี้ตั้งแต่มีการเปิดตัว Jank แล้ว
ผมอยากให้ ทางเลือกแทน JavaScript ในฝั่งฟรอนต์เอนด์ก้าวพ้นระดับ niche แล้วกลายเป็นของจริงเสียที
ผมอยากลองใช้ของอย่าง ClojureScript แต่ก็ยังนึกไม่ออกว่าจะใช้ที่ไหนนอกจากโปรเจ็กต์เล่นส่วนตัว ถ้าเป็นองค์กรที่แบ็กเอนด์ใช้ Clojure อยู่แล้วก็คงนำมาใช้ได้ง่ายกว่า
ผมยังไม่เคยใช้ในโปรดักชัน แต่เคย deploy โปรเจ็กต์ส่วนตัวไม่กี่ตัวกับของใช้ในบ้านอยู่บ้าง Reagent ซึ่งเป็น React wrapper ของ ClojureScript นั้นพูดตามตรงรู้สึกสมเหตุสมผลกว่า React เองเสียอีก
คุณสร้าง HTML ด้วย Hiccup และคอมโพเนนต์ก็เป็นแค่ฟังก์ชันภายใน Hiccup DSL ซึ่ง DSL นี้ก็แทบจะเป็น list อยู่แล้ว เลยทำให้ผลลัพธ์ออกมาสะอาดมาก สิ่งที่เป็น static ก็ดู static และสิ่งที่เป็น dynamic ก็ดู dynamic ชัดเจน รู้สึกว่ามีเวทมนตร์น้อยกว่า React ปกติมาก
สิ่งที่แย่คือตอนจะใช้คอมโพเนนต์ที่ไม่เป็นฟังก์ชันจาก NPM ไม่ถึงกับร้ายแรงแต่โค้ดจะดูไม่สวย แก้ด้วย wrapper ได้ แต่ไลบรารี JS บางตัวใน cljs นั้นสภาพตั้งต้นค่อนข้างรกมาก
ชุมชนก็เป็นมิตรและ成熟มาก
ลองเปลี่ยนสคริปต์ส่วนตัวก่อนเพื่อจับทางและสัมผัสข้อดี ไม่ได้ดีกว่าทุกกรณี แต่ภายหลังคนอื่นอาจมาขอคำแนะนำจากคุณ ดังนั้นคุณควรมั่นใจกับมันพอสมควร
เวลาจะนำเทคโนโลยีที่คนไม่คุ้นเข้ามา กลยุทธ์ที่ดีคือเลือกอะไรที่ไม่สำคัญมากมาเขียนใหม่แล้วปล่อยไว้ ถ้ามีปัญหาก็ย้อนกลับได้ง่าย และถ้าคนเริ่มชอบค่อยๆ ขยายเพิ่ม
ตอนก่อนเคยลักลอบนำ F# เข้าองค์กร .NET ผมก็เริ่มจากเขียนเทสต์ที่ไม่สำคัญนักด้วย F# ก่อน
https://blisswriter.app/
https://blog.nestful.app/p/how-we-dropped-vue-for-gleam-and
ผมไม่ได้ตาม cljs มานานแล้ว แต่จำได้ว่าเดิมทีมันถูกอธิบายประมาณว่าเป็น “Clojure บน JavaScript” อย่างน้อยก็เหมือน Rich จะเคยอธิบายแบบนั้นในช่วงแรก
ผมเข้าใจว่าเจตนาคือทำให้มันใกล้เคียงอีกหนึ่ง runtime ให้มากที่สุด
แต่การเปลี่ยนแปลงครั้งนี้ดูเหมือนจะเพิ่มความสามารถที่มีเฉพาะใน cljs และ
awaitก็ชนกับคีย์เวิร์ดในclojure.coreของ Clojure เองอยู่แล้วเลยสงสัยว่าสองอิมพลีเมนเทชันนี้ค่อยๆ แยกจากกันไปตามเวลา หรือว่าฟีเจอร์นี้สำคัญกับผู้ใช้มากพอให้ยอมรับความต่างนั้น
เรื่องนี้สำคัญเพราะทำให้จัดการ JavaScript interop ได้โดยไม่ต้องเพิ่มไลบรารีเสริม
เป็นฟีเจอร์ที่ขาดหายไปนานมาก และรีลีสนี้ก็น่ายินดีทีเดียว
ผมว่าการเอาฟังก์ชัน async/await ไปครอบด้วย CSP น่าจะเป็นวิธีจัดการที่ดีกว่า Clojure มีแพตเทิร์นที่ดีกว่านี้อยู่แล้ว
ไม่ได้หมายความว่า core.async จะหายไป และถ้า async/await เหมาะกว่าการใช้งานแบบ Promise บางทีส่วน
.cljsของ core.async ก็อาจถูกอัปเดตได้ต่อให้มี function hint ใหม่นี้เข้ามา วิธีนั้นก็คงไม่ได้หายไป
https://clojurescript.org/guides/promise-interop#using-promi...
ผมยังไม่แน่ใจว่าควรมองเรื่องนี้อย่างไร จุดสำคัญอย่างหนึ่งของ core.async ไม่ใช่ว่ามันควรดึงทุกอย่างเข้ามาอยู่ใน channel หรอกหรือ
ผมไม่แน่ใจว่าการมีคีย์เวิร์ด
asyncแบบ JavaScript นั้นเป็นการอัปเกรดจริงหรือเปล่าไม่ได้บังคับว่าต้องใช้ และคุณก็ยังใช้ core.async ต่อไปได้ อีกทั้งมันยังเป็นฟีเจอร์ที่ถูกขอมากที่สุดในแบบสำรวจ ClojureScript ครั้งล่าสุดด้วย