Clojurescript in my opinion

Chris
Chris’ Dialogue
Published in
3 min readApr 17, 2017

ช่วงวันสองวันที่ผ่านมาอยากจะลองศึกษา Clojurescript เลยพยายามลอง Re-write TodoMvc ให้ด้วย Clojurescript โดยใช้ Reagent framework

https://github.com/chrisza4/playgrounds/tree/master/clojurescript-react

พอลองเขียนตั้งแต่ต้นจนจบมีความเห็นที่อยากจะบันทึกไว้

The good

“Clojure เป็นภาษาที่มี Expressive power สูงมาก”

อาจจะงงกับคำว่า Expressive power บ้าง ผมขอขยายความก่อนว่าผมหมายถึงอะไร

คือถ้าเราคิดว่าการเขียนโปรแกรมคือการคุยกับคอมพิวเตอร์เนี่ย ผมยกตัวอย่างว่าคอมพิวเตอร์เป็นลูกน้องของเรา ชื่อน้องคอม ละกันนะ

เวลาเราสั่งงานใคร เราอยากคุยกับคนๆ นั้นให้

  1. สั้นและกระชับที่สุดเท่าที่จะทำได้
  2. แต่เข้าใจตรงกันและชัดเจนที่สุดเท่าที่จะชัดเจนได้ ไม่เข้าใจผิด

บางภาษานั้นการอธิบายบางอย่างอาจจะยืดยาวมากๆ (เช่น Assembly หรือ C) ต้องเขียน 3–4 บรรทัดกว่าจะสั่งงานน้องคอม แล้วน้องคอมยอมเข้าใจแล้วทำตามได้ ถ้าสั่งไม่ครบน้องคอมจะไม่ฟัง ก็อาจจะมาในรูปแบบของการคอมไพล์ไม่ผ่าน หรือโค้ดเบรคระหว่างทาง

ตรงข้าม บางภาษานั้นการอธิบายสิ่งที่เราอยากสั่งการให้น้องคอมทำเนี่ย มันอาจจะสั้น แต่มันไม่ชัดเจน ไม่แน่นอน เช่น เราเขียนแบบนี้ เราตั้งใจให้น้องคอมทำแบบนี้ มันดันทำอีกแบบนึงที่เรางงว่า “เห้ย ไม่ได้สั่งแบบนี้นะ” น้องคอมดันไปทำอีกแบบนึง

ภาษาที่มี Expressive power สูงจะมีทั้งสองอย่าง คือ เราสามารถสั่งการคำสั่งยากๆ ที่ซับซ้อนได้ด้วยคำสั่งที่สั้น แต่ในทางตรงข้าม คำสั่งที่สั้นนั้นก็ไม่น่าสับสนทั้งสำหรับคอมและสำหรับตัวเรา

ถ้านึกถึงว่าคำสั่งแบบไหนสับสนง่าย สั่งอย่างอาจจะทำอย่าง ลองนึกถึง SQL หรือคำสั่ง Read file ที่ต้องต่อกับ IO ครับ บางทีเราสั่งว่าอ่านไฟล์นี้ ปรากฎว่า อ้าว ตารางที่เราขอถูกลบไปแล้ว อ้าว ไฟล์ไม่มี มัน Throw error ที่เราไม่คาดคิดมา

ยังงี้แหละคือ สั่งอย่างทำอย่าง สั่งงานมันหนึ่งอย่าง มีความเป็นไปได้ที่จะเกิดอะไรขึ้นเยอะมาก ไม่ชัดเจนว่าสั่งแบบนี้ได้แบบนี้แน่ๆ

(อาจจะสงสัยว่าสั่งอ่านไฟล์แล้วจะไม่ Throw ได้ยังไง อืม ถ้าเป็น Functional programming แท้ๆ พวก IO มันจะอยู่ถูกจำกัดจำเขี่ยให้ใช้น้อยๆ ทำให้โค้ดเรามีส่วนที่มีคำสั่งที่ไม่คาดคิดสั่งน้องคอมอย่างนึง น้องคอมทำอีกอย่างนึงน้อย)

อันนี้แหละคือสิ่งที่ผมเรียกว่า Expressive power สั่งงานน้องคอมได้สั้น กระชับ ชัดเจน

และ Clojure นั้นมี Expressive power สูงมาก ด้วย Syntax ง่ายๆ อย่าง [ ( { และเครื่องหมายวงเล็บปีกกา ผมสามารถอธิบายให้คอมพิวเตอร์สร้าง Todo-app ได้ด้วยจำนวนตัวอักษรและโค้ดที่น้อยกว่า Javascript เยอะมาก

และทุกๆ คำสั่งเวลาผมอ่านก็ไม่รู้สึกเลยว่าตรงไหนเป็น Magic (ยกเว้นเรื่องสโคปของ Atom ที่จะบ่นในท้ายบล็อก) ทุกคำสั่งสามารถถอดออกมารันข้างนอก REPL แล้วไม่มีความสับสนว่าแบบนี้จะออกได้หลายหน้าเลย สั้น กระชับ และตรงประเด็นมากในการใช้สื่อสารกับคอมพิวเตอร์

อันนี้คือข้อที่รู้สึกว่าเจ๋งมาก

อีกข้อที่เจ๋งมากเลยคือ มันสามารถแยก State ออกจาก Component ได้โดยไม่ต้องใช้ MobX หรือ Redux เลย

ถ้าดูตัวอย่าง ผมมีสองไฟล์

ถ้าใครอ่านไม่ออก ประเด็นหลักที่ผมจะบอกคือ

  • ไฟล์ todo-state ผมไว้เก็บ todo-data ไว้เป็น atom (ของที่มันเปลี่ยนได้ใน React จะเก็บในรูป Atom เวลา Atom เปลี่ยน จะเหมือนรัน setState)
  • ไฟล์ todo-app บรรทัดที่ 6 ผม require todomvc.state เข้ามา
  • ไฟล์ todo-app จะเห็นว่าผมสามารถใช้ todo-dataใน todomvc.state ในการสร้าง todo-app ได้ (บรรทัด 46)
  • และที่มหัศจรรย์กว่านั้นคือ ผมสามารถสั่ง add-todo ซึ่งเป็นคำสั่งใน todomvc.state เพื่อเปลี่ยนทั้งข้อมูลได้ แล้ว React ก็ re-render ให้ด้วย
  • ทำให้การแยกระหว่าง State management กับ React component มันมาในตัว ClojureScript เลย ไม่เหมือน javascript ที่การเปลี่ยนข้อมูลของ React component ต้องใช้ setState ภายในตัว Component เอง หรือไม่งั้นก็ต้องทำผ่าน Redux/MobX

The bad

  • Scope ของ r/atom มันชวนสับสนมากเลยเวลาใช้ let ถ้าเราจัดสโคปไม่ดี อันนี้โดน Surprise ไปหลายรอบแล้ว เช่น

จะเห็นว่าบรรทัดที่สอง ผมประกาศ todo-filter โดยใช้ let (ซึ่งเป็นการบอกว่ามันอยู่ในสโคปเล็กๆ ตรงนี้ อันนี้เป็น Opinion ว่าเหมือน Clojurescript มันดีมากตรงที่เราอยากให้สโคปของ atom หรือ variable มันแคบแค่ไหนก็ได้ทั้งนั้น เป็นทั้งข้อดีข้อเสียเรื่องนี้ยังบอกไม่ได้ว่าดีไม่ดี)

ซึ่งปกติตามที่ผมเข้าใจมันควรจะเป็นว่า

“เมื่อ Atom เปลี่ยนแล้ว React component ทุกตัวที่ Reference atom นั้น จะต้องถูก Render ใหม่”

เช่นในที่นี้ เรามีคำสั่งเปลี่ยนในบรรทัดที่ 25 และเรามี Reference atom ในบรรทัดที่ 22 เมื่อรัน (reset! todo-filter %) มันก็ควรจะเปลี่ยนให้ @todo-filter เปลี่ยนไป แล้วก็บังคับให้ todo-footer มัน re-render ใหม่

เพราะใน Doc เขาก็เขียนไว้ว่า การเปลี่ยน Atom จะทำให้ React component ที่ Reference atom นั้นเรนเดอร์ใหม่อีกรอบ เออ ก็น่าจะเป็นงั้นสิ

แต่ไม่เป็นแบบนั้นเสมอไป!!!!!

ถ้าเราจัดสโคปไม่ดี มันไม่ได้ทำตามกฎที่ผม Expect ไว้นะ เช่น กรณีตัวอย่าง (ขอยกตัวอย่างเลย เพราะถึงทุกวันนี้ก็ไม่เข้าใจว่าทำไมมันไม่ทำตามนั้น)

จะเห็นว่าผมแค่ย้าย Scope ของ todo-filter มาไว้ใน Let ข้างใน (บรรทัดที่ 2 อันเก่า ย้ายมาบรรทัดที่ 4 อันใหม่) แต่แบบนี้ตอนรัน reset! todo-filter ตัว component นี้จะไม่ได้รับ atom ใหม่!! (เหมือนมันรันแล้วพยายามเซ็ตกลับไปเป็น all อีกครั้งตลอด)

ไม่ค่อยเข้าใจเหมือนกันว่าทำไมถึงเป็นแบบนี้

  • และก็มีเรื่อง re-render scope ที่มันสับสนมากๆ จนคนตั้ง Issue หลายคน อันนี้เจอมาเหมือนกัน ไปอ่านพวกนี้มาถึงจะเริ่มเก็ต https://github.com/reagent-project/reagent/issues/224 ในนี้มีบทความสามอันเลยที่อธิบายว่า Reagent จะ Re-render ตอนไหน และมันไม่รู้สึกว่าชัดเจนเท่า React แฮะอันนี้
  • Repl เซ็ตอัพยาก Tutorial ไม่สื่อเท่าไหร่ ถึงตอนนี้ก็ยังไม่สำเร็จ ไม่ใช้ไปแล้ว (แต่ถ้าใช้ได้จะเป็นเครื่องมือการพัฒนาที่ดีมากเลยสำหรับ Clojure)
  • อีกข้อนึงคือ ใช้ React component ใน NPM ได้ยาก ข้อนี้สำคัญมากถ้าจะทำ Project ให้ลูกค้า แต่ถ้าทำ App ยาวๆ แบบ Maintain กันไปเป็นปีสองปี ทำ Startup อะไรแบบนี้ จากประสบการณ์ส่วนมากสุดท้ายเราก็จะเขียนใหม่หมดอยู่ดี การใช้ Library ข้างนอกจะจำกัดการ Customization ของเราไปเยอะ และเราต้องการ Customize เยอะมากถ้าเราทำแอพแบบ Sass อ่ะนะ แต่ถ้าปั่นงานให้ลูกค้าแบบรับจ้างเขียน การมี Access หา Library นอกได้เยอะๆ เป็นเรื่องสำคัญเลยแหละ

วิธีการ Access NPM มีสองวิธี

  1. ไป require จากของที่ Community Clojurescript port ไว้ให้ ใน http://cljsjs.github.io/
  2. ใช้ท่าพิสดารในการเอา React component ไปใส่ใน window object ตามนี้ http://blob.tomerweller.com/reagent-import-react-components-from-npm เป็นท่าที่ Hack มาก เอา React component ไปวางไว้ใน Global object ก่อนที่จะโหลด Clojurescript ขึ้นมาจริงๆ (ซึ่งผมว่าไม่สเกลเท่าไหร่ มันชวนสับสนมากๆ ทั้ง Ordering ในการ Import ทั้งว่าตกลงเรามี React component ตัวนี้หรือยัง จะรู้ได้ยังไงว่ามีแล้ว ก็เราไม่ได้ require ที่หัวแต่ดันโผล่ขึ้นมาให้ใช้ราวกับเวทย์มนตร์)

สรุป

  • เป็นภาษาที่น่าสนใจมากๆ และคิดว่าถ้าเราทำแอพใช้เอง ก็ใช้ขึ้น Production ได้ทีเดียว
  • Learning curve ไม่เยอะอย่างที่หลายๆ คนกลัวพอเห็นวงเล็บจำนวนมาก (นี่คือผมจากไม่เป็นจากศูนย์ ใช้เวลาสามวัน วันละ 2–4 ชั่วโมงก็เขียนได้สำเร็จ ถ้าเทียบกับภาษาอื่นที่เคยหัดมาผมว่าอันนี้เรียนได้เร็วเลยนะ)
  • แต่พลัง Expressive power ที่ Trade ด้วยการมี Community น้อย ทำให้ความเห็นส่วนตัวมองว่า ถ้าเริ่มทำ Product คิดว่าเล่นไหว แต่ถ้าทำ Project ที่ต้องส่งงานลูกค้า การที่ต้องเขียนใหม่หลายอย่างเสียเวลาเกินไป
  • เขียนแล้วสนุกตื่นเต้นดีกับการอธิบายสิ่งยากๆ ได้อย่างชัดเจนและแม่นยำขนาดนี้
  • ยกเว้น Scope ขอเน้นอีกที Scope ของ Atom เป็นเรื่องเดียวเลยที่สับสนอลหม่านและไม่ค่อยชัดเจนเท่าไหร่ ไม่น่าใช่ตัวผมสับสนคนเดียว เพราะมี Issue ซ้ำหลายอัน แล้วก็มีบทความอธิบายเฉพาะเรื่องนี้ตั้ง 3 บทความ
  • ถ้าไม่คิดอะไรมากก็ไม่ต้องใช้ Let ประกาศ Atom ใช้ def ให้หมด ก็ทำได้แหละ แต่….
  • ผมคิดว่าพลังของ Clojure มันอยู่ที่ let และการปิดสโคปการใช้ตัวแปรให้เป็นเท่าที่จำเป็นจริงๆ เสมอ (ไม่รู้มโนไปเองหรือเปล่า แต่รู้สึกได้เลยว่านี่เป็นพลังที่ไม่มีในภาษาอื่น เพราะเราใช้ let กำหนดสโคปของแต่ละ Variable ให้เล็กแค่ไหนแค่กี่บรรทัดก็ได้ แถมไม่น่าเกลียดแบบภาษาอื่นที่ต้องปีกกาเปิดปีกกาปิดไปเรื่อย อันนี้มันแค่วงเล็บอ่ะ มันก็มีเยอะอยู่แล้ว เพิ่มอันนึงก็ไม่ทำให้น่าเกลียดอะไร)
  • เอาล่ะ แค่นี้พอละ บาย

--

--

Chris
Chris’ Dialogue

I am a product builder who specializes in programming. Strongly believe in humanist.