Tag Archives: Clojure

ตัวอย่างผลจากแนวคิด isolation ของ Clojure

คัดลอกมาเก็บไว้จากคอมเมนต์ตอบพี่ป้อ อันนี้ (มีเพิ่มเติมอีกนิดหน่อย)

เริ่ม —-

isolation มันฝังอยู่ในทุกๆส่วนของ Clojure ทั้งตัวภาษาและ ecosystem เช่น
– การแยกภาษาจาก runtime ทำให้สร้าง clojurescript ได้ง่าย
– การแยก ผู้ส่งกับผู้รับออกจากกันด้วย channel ของ core.async (ไม่เลือกทางที่ต้องรู้ผู้รับแบบ actor)
– การแยก check/validation ออกมาเรียกใช้เมื่อไหร่ก็ได้ ที่ไหนก็ได้ของ clojure.spec ถ้าไม่อยากใช้ก็ไม่บังคับ
– การแยกวิธีการประมวลผลของ transducer ทำให้เอามันไปใช้ได้กับ sequence หลายประเภท ทั้ง list, vector ธรรมดา, channel และอื่นๆ
– ทุกอย่างมี namespace (แม้กระทั่ง keyword) ทำให้ชื่อเดียวกันของต่าง component กัน ไม่จำเป็นต้องเป็นสิ่งเดียวกันเสมอไป
– เน้นการสื่อสารด้วย data ซึ่งแปลว่าแต่ละ component สามารถตีความหมายของ data ก้อนหนึ่งๆ ได้ตามความต้องการ
– การที่ Datomic แยก database ออกจาก storage ทำให้มันทำงานได้ทั้งบน memory, sql, DynamoDB, Cassandra, … แยก transactor ออกจาก query engine ทำให้ scale query engine ได้ทันที (ต่อตรงไปที่ storage)
– พวกไลบราลีต่างๆ ก็ค่อนข้างทำงานเฉพาะ เช่น ไลบราลีสำหรับ routing ที่ทำหน้าที่ parse url เป็น data อย่างเพียงอย่างเดียว
– Type มี polymorphism แต่ไม่มี inheritance เพราะ inheritance มันทำให้เกิดการผูกกันที่แน่นเกินไป

Isolation เป็นสิ่งที่ทั้ง community เห็นความสำคัญตรงกัน ในทาง implementation บางคนอาจทำได้มากได้น้อยก็แล้วแต่ความสามารถและประสบการณ์ของแต่ละคน แต่มันไปในทิศทางเดียวกัน บางอย่างภาษาอื่นอาจจะมีสิ่งที่คล้ายๆ กัน แต่ก็น่าจะไม่ได้รับความสำคัญเทียบเท่า

ผมว่าเรื่องนี้เป็น value ที่สำคัญมาก และอธิบายยากมากของ Clojure เวลาใครมาถามว่า Clojure ดีอย่างไร ผมจะรู้สึกว่ามันอธิบายยาก เพราะ syntax มันเป็นแค่ส่วนเดียว แต่จริงๆ แล้วความเชื่อใน simplicity และ isolation ของ community และทุกคนมุ่งไปทางนั้นมันสำคัญกว่ามาก

—จบ

Designing is fundamentally about taking things apart. It’s about taking things apart in such a way that they can be put back together (If that makes sense).
So separating things into things that can be composed that’s what design is.
— Rich Hickey

การแยก Identity, Value และ State ออกจากกันของ Clojure มีประโยชน์อย่างไร

จากคำถามของน้องบอสวันนี้ (โพสเต็มๆ อยู่ที่นี่ครับ)

เมื่อกี้พยายามอธิบายเรื่อง atom กับการ decouple identity กับ value ออกจากกัน ว่ามันมีประโยชน์ยังไง แต่รู้สึกว่ายังไม่เข้าใจประโยชน์ของมันจริงๆ ทุกคนมองยังไงกันครับ?

ด้านล่างนี้เป็นความพยายามในการอธิบายคำถามนี้ของผมครับ

ผมคิดว่าประโยชน์ของการแยก identity กับ value คือทำให้คนที่ observe value ของ identity แต่ละช่วงเวลายังเห็นภาพที่ถูกต้องของช่วงเวลาที่ตัวเองสนใจอยู่เสมอ

มาคุยเรื่องคำศัพท์กันก่อน เผื่อคนที่เพิ่งเคยได้ยิน

สิ่งๆหนึ่ง มีการเปลี่ยนแปลงคุณลักษณะ ไปตามเวลา
สิ่งหนึ่ง คือ identity
คุณลักษณะ คือ value
คุณลักษณะ ณ ช่วงเวลาหนึ่ง คือ state
เช่น ปีที่แล้วนาย ก. สูง 164 หนัก 61 ตอนนี้นาย ก. สูง 165 หนัก 60

  • identity คือ นาย ก.
  • value ขอมองว่ามี 2 value ก่อนคือ สูง 164 หนัก 61 และสูง 165 หนัก 60
  • state ถ้าเรามองที่เวลาปัจจุบัน คือ สูง 165 หนัก 60

Atom คือ state นั่นเอง มันใช้ว่าบอกว่า ณ เวลาปัจจุบัน identity มีค่าเป็นเท่าไหร่

ถามว่าประโยชน์คืออะไร ก็ต้องมาคุยว่าปกติที่เราไม่แยกสิ่งเหล่านี้เราทำกันอย่างไร

  1. เราก็จะมีที่ๆ นึงเก็บค่าปัจจุบันของนาย ก. ซึ่งจะโดนเขียนทับไปเรื่อยๆ ไม่มีการเก็บของเก่า อยากเก็บก็ต้องจัดการเอง มันอาจจะไม่ยากนักถ้ามีแค่ 2 ค่าแบบส่วนสูงและน้ำหนักในตัวอย่าง แต่ถ้าสิ่งที่เราสนใจนั้นมีชั้นความซับซ้อนสูงหละ เช่น เรามองภาพของประชากรทั้งโลกที่ประกอบด้วย ประชากรแต่ละประเทศ ประชาชนแต่ละเมือง ซ้อนกันไปเรื่อยๆ เราจะทำการเก็บของเก่าอย่างไร
  2. ถ้ามีการประมวลผลใดๆ กำลังทำงานอยู่ ณ เวลาที่ค่าของนาย ก. กำลังจะเปลี่ยน การประมวลผลนั้นอาจจะทำงานผิดพลาดได้
    ตัวอย่างความผิดพลาด เช่น เราบังเอิญไปคำนวณ BMI ของนาย ก. ตอนจังหวะมันจะเปลี่ยนค่าพอดี
    เช่น ตอนที่นาย ก. สูง 164 หนัก 61 คำนวณ BMI ได้ 22.68 แต่ขณะที่กำลังจะคืนค่ากลับไป นาย ก. ดันเปลี่ยนไปสูง 165 หนัก 60 พอดีซึ่งแปลว่าการประมวลผลครั้งน้ันผิดพลาดวิธีการป้องกันไม่ให้การประมวลผลใดผิดพลาดที่เค้าทำกันมามีความซับซ้อน เช่น locking ด้วยเทคนิคต่างๆ ซึ่งจากความซับซ้อนของมันอาจจะก่อให้เกิดความผิดพลาดในด้านอื่นได้อีก และมันยังทำให้ยากต่อการทำความเข้าใจระบบของเราด้วย เช่น ลองคิดดูว่าถ้าเราคำนวณ BMI ผิด ความคิดแรกสุดเลยเราคงไม่คิดหรอกว่าเกิดจากการโดนแก้ค่าขณะกำลังคำนวณพอดี เราคงคิดว่าวิธีการคำนวณ BMI ของเรามันผิด เราก็อาจจะต้องไปพยายามไล่สาเหตุอยู่นานกว่าจะเจอปัญหาที่แท้จริง ลองคิดภาพเรื่องนี้ในการประมวณผลของระบบใหญ่ๆ ที่มีความซับซ้อนสูงดู

อ่านเพิ่มเติม Working Models and Identity

Code as Data มีประโยชน์อย่างไร

มีคนเล่ามาทาง FB Message ว่า กำลังศึกษา Clojure อยู่ แต่ไม่ค่อยเข้าใจประโยชน์ของแนวคิด code as data

ผมเองก็ยังรู้สึกไม่ค่อยเข้าใจเหมือนกัน แต่ขอแชร์สิ่งที่ผมพอมองเห็น พอเข้าใจนะครับ

1. มันทำให้มี syntax เฉพาะของภาษาน้อย เพราะจำกัดอยู่แค่ data structure ของภาษา โดยทุกๆ อย่างมันอยู่ใน รูปนี้หมด


(form arg1 arg2 arg3 ... argN)

เช่น def, defn, if, let ก็อยู่ในรูปนี้ทั้งหมด มัน consistent ดี

2. ไม่มี syntax ไม่เคยต้องเรียน syntax ใหม่ มีแต่ไลบราลีใหม่ อะไรที่เติมเข้ามาในภาษาแค่เราเข้าใจว่า ตัวแรกในวงเล็บ(form) มันทำอะไรก็จะทำให้เข้าใจมันทันที

3. พอ code เป็น data แล้วก็แปลว่าสามารถใช้ tool ที่ manage data มา manage code ได้ เช่น เวลาเขียน macro

4. Parser ของภาษาก็เขียนง่าย แล้วเวลาเขียนพวก tool เช่น refactoring tool ให้ ide, editor ก็ใช้ไลบราลีที่ manage code นี่แหละในการเขียน tool พวกนั้น

5. ส่งโค้ดผ่าน network ง่าย serialize ง่าย อาจจะไม่ใช่สิ่งที่ทำบ่อยๆ แต่เราสามารถเขียนระบบแบบส่ง code ไปรันบน server อื่นได้ เช่น network repl

6. นอกจาก code as data แล้ว เค้ายังนิยมออกแบบอย่างอื่นให้เป็น as data อีก เช่น config, html ทุกอย่างก็จะ consistent กันหมด ใช้ tool เดิมแนวคิดเดิมจัดการทุกอย่าง

เขียนครั้งแรกที่ https://www.facebook.com/groups/clojurethai/permalink/1235319406506550/

เชื่อมต่อ Clojure REPL เข้าไปที่ VM ที่ทำงานอยู่

คนที่เขียน Java มาอาจจะเคยต้อง restart server เป็น debug mode เพื่อต่อ debugger ของ IDE เข้าไป debug ปัญหา โดยมีความยุ่งยากคือต้อง restart ซึ่งอาจจะรบกวนการใช้งานของ user หรือบางครั้งหลังจาก restart แล้วปัญหาก็ดันหายไป

คนที่เขียน Ruby มาอาจจะเคย start irb บน server เพื่อลองรันคำสั่งต่างๆ แต่มันก็เป็นคนละโปรเซสกับที่ server ทำงานอยู่

บน Clojure เราสามารถเปิด port เอาไว้เชื่อมต่อ repl เข้าไปที่ VM หรือ server ที่ทำงานอยู่ได้ง่ายๆ โดยใช้ฟีเจอร์ที่ชื่อว่า Socket Server ซึ่งถูกเพิ่มเข้ามาใน Clojure เวอร์ชั่น 1.8

การใช้งานทำได้โดยการเพิ่ม option นี้เข้าไปเวลา start JVM

-Dclojure.server.repl="{:port 5555 :accept clojure.core.server/repl}"

* :accept คือ ฟังก์ชันที่จะถูกเรียกเมื่อมีการติดต่อเข้ามาทาง socket

ในที่นี้ผมขอยกตัวอย่างรัน repl ปกติขึ้นมาหนึ่ง vm พร้อมเปิด socket ให้เชื่อมต่อ repl เข้ามาเพิ่มได้อีกละกัน

ปกติผมจะ start repl ด้วยคำสั่งนี้

$ rlwrap java -jar /path/to/clojure.jar

เราก็ทำการเพิ่ม option ข้างต้นเข้าไป

$ rlwrap java -Dclojure.server.repl="{:port 5555 :accept clojure.core.server/repl}" -jar /path/to/clojure.jar

เมื่อทำการรันคำสั่งด้านบนนี้ repl จะถูก start ขึ้นมาปกติ เมื่อ start เสร็จแล้ว เราสามารถ connect เข้าไปที่ VM นั้นผ่าน socket ได้โดยใช้ telnet ดังนี้

$ telnet localhost 5555

เราก็จะได้ repl ขึ้นมาอีกอันซึ่ง share data และ memory เดียวกับ repl แรกทั้งหมด เราสามารถทำทุกอย่างได้ตามชอบใจกับ VM นั้น

รายละเอียดเพิ่มเติมที่ Launching a Socket Server

เขียนครั้งแรกที่ https://www.facebook.com/groups/clojurethai/permalink/1231413573563800/

Clojure กับการพิสูจน์ตัวเอง

ผมเคยมีปมในใจอยู่เรื่องหนึ่ง การหัดใช้งานภาษา Clojure จนอยู่ในระดับที่ใช้งานได้อย่างมั่นใจช่วยผมแก้ปมในใจนั้นได้

ผมหัดเขียนโปรแกรมจริงจังตอนสมัยเรียนมหาวิทยาลัยปีหนึ่ง เป็นช่วงปิดเทอมก่อนเปิดภาคเรียนที่สอง ซึ่งเทียบกับเพื่อนร่วมสาขา และโปรแกรมเมอร์ต่างชาติหลาย ๆ คนที่ผมรู้จัก ผมรู้สึกว่าผมเริ่มเขียนโปรแกรมช้ามาก จริง ๆ แล้ว ผมก็ไม่ได้คิดจะเพิ่งเริ่มตอนปีหนึ่งนี้หรอก ผมเคยซื้อหนังสือ Visual Basic มาพยายามที่จะหัดสมัยมัธยมปลาย แต่ก็ล้มเหลว ผมหัดไม่สำเร็จ รู้สึกว่าไม่เข้าใจอะไรเลย มันยากเกินไป

ในช่วงปีหนึ่งนั้นผมหัดเขียนภาษา Java ตามที่มหาวิทยาลัยกำหนดให้ได้เรียน ได้เพื่อนสอน ได้อาจารย์สอน ได้ใช้งานมันมากขึ้นเมื่อเข้าเรียนในภาควิชาคอมพิวเตอร์ จนกระทั่งจบมาในช่วงแรกก็ใช้ Java ทำงานเป็นหลัก ทำให้ผมมีความมั่นใจในการใช้งานภาษา Java ในระดับหนึ่ง

หลังจากที่ผมทำงานไปได้ซักพัก ผมเริ่มหาภาษาเขียนโปรแกรมอื่น ๆ มาศึกษา อันเนื่องมาจากเหตุผลหลายอย่างที่ขอยังไม่เล่าในตอนนี้เพราะเดี๋ยวจะออกนอกเรื่องไปไกล ตอนนั้นผมเลือกภาษา Scala เป็นภาษาที่ผมจะหัดเป็นภาษาต่อไป เพราะผมเคยเรียน Functional programming ด้วยภาษา Haskell มาบ้างสมัยเรียนในมหาวิทยาลัยซึ่งผมก็ไม่ค่อยเข้าใจหรอก ที่เลือก Scala เพราะตอนนั้นมันโฆษณาว่ามันเป็นภาษา hybrid ที่รวมทั้งแนวคิด Object-Oritend programming (OOP) และ Functional programming (FP) เข้าด้วยกัน ตอนนั้นผมคิดว่าการเอาสองแนวคิดมารวมกันมันต้องเจ๋งมากๆ เลย ภาษาที่เขียนได้สองแบบก็น่าจะได้เปรียบ ผมโหลดหนังสือ Programming in Scala เถื่อนมาปรินท์อ่าน cover to cover จนจบ เข้าใจบ้างไม่เข้าใจบ้าง พอจบแล้วผมเริ่มหาโจทย์มาหัดเขียน ตอนนั้นผมเลือกหัดเขียนเว็บ โดยผมศึกษา Lift ซึ่งเป็นเว็บเฟรมเวิร์คของ Scala ที่ดังในสมัยนั้น ผลปรากฏณ์ว่าไม่ประสบความสำเร็จ มันยากไป ผมไม่เข้าใจอะไรเลย

ผมไปโหลดวิดีโอสอน Haskell ของ Erik Meijer มาดูหวังว่าจะได้เข้าใจพื้นฐาน FP เพื่อจะเอาไปรวมกับความรู้ OO ที่พอมีอยู่บ้างอาจจะทำให้เข้าใจ Scala ได้มาขึ้น ผมดูวิดีโอถึงเรื่อง Functor แล้วผมก็รู้สึกว่าผมไม่เข้าใจเลย และก็หยุดดูไป

หลังจากนั้นผมได้มีโอกาสทำงานที่ใช้ภาษา Ruby ร่วมกับทีมที่ทำ pair programming กันเป็นประจำ ซึ่งทำให้ผมศึกษาภาษา Ruby ได้อย่างรวดเร็วมาก ตอนนั้นผมคิดว่าทำไม Rails มันเข้าใจง่ายกว่า Lift ขนาดนี้ เพียงแค่เวลาประมาณ 1 ปีผมรู้สึกว่าผมเข้าใจ Ruby มาก ๆ

เมื่อรู้สึกมั่นใจในการเขียนภาษา Ruby แล้ว ผมเริ่มมองหาภาษาต่อไปที่จะศึกษาอีกครั้ง ผมเห็น blog อันหนึ่งมีคนเค้าทดลองเขียนและเปรียบเทียบระหว่างภาษา Scala กับ Clojure ซึ่งในตอนแรกที่ผมอ่าน ผมมั่นใจว่าผลลัพธ์จะต้องออกมาเป็นว่า Scala ดีกว่าแน่นอน แต่ blog นั้นก็ทำให้ผมประหลาดใจเพราะในตอนท้ายผู้เขียนสรุปว่าเค้าชอบภาษา Clojure มากกว่า ตอนนั้นผมคิดว่าภาษา syntax ประหลาดนี่อะนะ ภาษาที่ใช้ syntax เหมือนภาษาที่เกิดตั้งแต่ 40-50 ปีที่แล้วนี่เหรอ จะดีกว่าได้ยังไง ภาษา paradigm เดียวจะดีกว่าภาษา 2 paradigm ได้ยังไง

จุดสำคัญต่อมาที่ทำให้ผมสนใจภาษา Clojure คือมันมีหนังสือที่ชื่อ Joy of Clojure ผมอยากรู้ว่าความ joy นี้มันเป็นอย่างไร แม้กระทั่งภาษา Ruby ที่เค้าบอกว่า optimize for programmer happiness ยังไม่เห็นมีคนเขียนหนังสือ Happy Ruby เลย แล้ว Clojure นี่มันจะ joy ได้ซักขนาดไหน ผมเป็นคนที่แสวงหาความสุขจากการเขียนโปรแกรมอยู่เสมอ อันนี้ก็ขอเก็บไว้เอาไปเขียนได้อีกเรื่องหนึ่งเหมือนกัน ผมตัดสินใจซื้อหนังสือ Joy of Clojure มาอ่าน(รอบนี้ผมซื้อแล้ว😄) ผมอ่านไปได้ประมาณ 1 บทและเปิดข้าม ๆ จนจบเล่ม ผมหาความ joy ไม่เจอ ผมไม่เข้าใจความ joy ของ Clojure ผมไม่เข้าใจ Clojure

ลองนึกย้อนกลับไป ผมพบว่าผมไม่เคยหัดเขียนภาษาไหนได้ประสบความสำเร็จด้วยตัวเองเลย ตั้งแต่ VB, Scala, Haskell มาจนถึง Clojure ภาษาที่ผมเขียนได้อย่างมั่นใจมีแต่ภาษาที่มีคนสอน* อย่างเช่น Java กับ Ruby ผมไม่มีความสามารถในการศึกษาหาความรู้ด้วยตัวเองเหรอ ผมอิจฉาหลาย ๆ คนที่ผมมีโอกาสได้รู้จัก เค้าเหล่านั้นเปลี่ยนอาชีพมาเป็นโปรแกรมเมอร์ทั้ง ๆ ที่ไม่ได้เรียนจบสายคอมพิวเตอร์มา เค้ายังทำได้และเป็นโปรแกรมเมอร์ที่เก่งอีกด้วย ผมทึ่งและอิจฉาในความสามารถของคนกลุ่มนี้มาก ไม่ใช่แค่เรื่องเขียนโปรแกรมเพียงอย่างเดียวผมยังชื่นชมคนที่หัดทำสิ่งยาก ๆ ด้วยตัวเอง (self learner) อย่างอื่น อย่างเช่นการหัดเล่นเครื่องเล่นดนตรีอีกด้วย ผมอยากทำแบบนี้ได้บ้าง ผมอยากพิสูจน์ได้ว่าตัวผมมีความพร้อมที่จะใช้ชีวิตบนโลกนี้ต่อไป โดยไม่ต้องรอให้ใครมาป้อนอะไรให้ ผมอยากที่จะสามารถหัดและทำในสิ่งที่ผมอยากทำ อยากรู้ได้ด้วยตัวเอง

ผมจึงตัดสินใจใช้ภาษา Clojure เป็นการพิสูจน์ตัวเอง เนื่องจากมันมีความต่างและผมรู้สึกว่ามันยากเพียงพอที่ถ้าผมทำได้สำเร็จผมจะมั่นใจว่าผมไม่ได้โกงโดยการเอาสิ่งง่าย ๆ มาหัด ผมซื้อหนังสือ Programming Clojure มาอ่าน ผมทำแบบฝึกหัด 4clojure ซึ่งเป็นแบบฝึกหัดที่ทำให้ผมเริ่มมองเห็นความ joy ในภาษา Clojure โดย ณ เวลานั้นผมชอบเขียนภาษา Ruby มาก หนึ่งในฟีเจอร์ของ Ruby ที่ผมชอบมากที่สุดคือ การใช้งานเมท็อดต่าง ๆ ใน class Enumerable เช่น map, select, inject, group_by และอื่น ๆ อีกมากมาย ซึ่งจริงๆ แล้วมันคือ กลุ่มของเมท็อดที่ได้รับอิทธิพลมาจาก FP ที่พอได้มาทดลองแก้ปัญหาใน 4clojure แล้ว พบว่าสิ่งที่ผมชอบใน Ruby มันเก่งยิ่งกว่าขึ้นไปอีกบน Clojure  ทุกอย่างสั้น กระชับ และเชื่อมต่อกันได้อย่างแนบเนียนมาก และผมก็ติดใจภาษา Clojure ตั้งแต่นั้นเป็นต้นมา

หลังจากที่ผมเริ่มเขียน Clojure เป็น ใช้เวลาอีกซักพักใหญ่กว่าผมจะมีโอกาสได้ใช้งานมันในงานจริง ๆ หลังจากนั้นไม่นานผมก็รู้สึกว่าผมเข้าใจและมั่นใจในการใช้งาน Clojure เพียงพอที่จะบอกกับตัวเองได้ว่า ผมประสบความสำเร็จในการเรียนรู้ด้วยตัวเองแล้ว ผมทำได้แล้ว

•ขณะที่เขียน blog  นี้อยู่ผมนึกขึ้นมาได้ว่าจริง ๆ แล้วก่อนหน้า Clojure มีอีกภาษาหนึ่งที่ผมหัดเขียนเองจนถือว่ามั่นใจระดับหนึ่ง คือ Prolog แต่ตอนนั้นผมได้ใช้ Prolog แค่เพียงเวลาสั้น ๆ ผมจึงยังไม่รู้ไม่มั่นใจว่าตัวเองเข้าใจ Prolog เพียงใด ภาษานี้เลยหายไปจากความคิดในตอนที่ตัดสินใจหัด Clojure

How Clojure teaches me about side-effects

ดัดแปลงมาจากคอมเมนต์นี้ https://www.facebook.com/groups/919377878100706/permalink/1121787907859701/?comment_id=1121803797858112&reply_comment_id=1121826181189207

หลังจากที่ผมโพสว่า Clojure สอนผม ว่าผมจะอยู่ร่วมกับ side-effects ได้อย่างไร

พี่มาคอมเมนต์ Patraphong Sukhonpitumart ถามว่า

clojure สอนยังไง มีตัวอย่างมั้ยครับ

ผมเลยตอบว่า

ที่ผมพอจะนึกออกตอนนี้ ไม่ได้ exclusive สำหรับ clojure นะครับ

  • สอนให้ model domain เป็น value ไม่ใช่ object มันเหมาะกับแนวคิด bounderies (Functional Core, Imperative Shell) มาก
  • แยกที่เก็บ mutable data (atom) ให้ชัดเจน เราจะเรียกใช้แก้ไขมันเมื่อไหร่ก็ได้ แต่ก็จะบอกว่าพยายามให้มีให้น้อยที่สุด
  • ไม่มีคนทำ ORM แล้วประสบความสำเร็จ
  • Referential transparency จะใช้ไม่ได้ถ้ามี side-effects ฟังก์ชันอย่าง memoize ก็จะทำงานผิด ถ้าไม่สนใจเรื่องนี้
  • ความที่เราสามารถทำ side-effect ในฟังก์ชันได้ตามใจ แต่เค้าก็จะบอกว่าไม่ควรทำ เช่น ฟังก์ชันที่ใช้ update ค่าให้ atom (เพราะมันไม่การันตีว่าจะถูกเรียกแค่ครั้งเดียว)
  • ไม่สร้างความยุ่งยากให้เราหากเราจะทำ side-effects ง่ายๆ เช่น เราอยากจะ println เมื่อไหร่ก็ได้ ไม่โดน type บังคับ
  • พวก React wrapper ก็จะบอกให้เราระวังเรื่อง state โดยที่บางตัวก็ strict บางตัวก็ปล่อยหลวมๆ
  • def เป็น mutable เพราะมันไม่จำเป็นต้อง immutable
  • do, doseq ทำให้เรารู้ว่าเราทำ side-effects อยู่นะคือ ผมมีโอกาสเลือกอยู่เสมอว่าผมจะทำ side-effects ได้ แต่ผมก็จะเห็นชัดเจนว่าถ้าผมทำแล้วปัญหาที่ตามมาคืออะไร

เลือกพฤติกรรมของฟังก์ชันด้วยพารามิเตอร์ฟังก์ชัน

Clojure เป็น functional language ซึ่งหมายความว่าเราสามารถส่งฟังก์ชันเป็นพารามิเตอร์ได้ ตัวภาษาได้มีการออกแบบไลบราลีมาตรฐานโดยใช้ประโยชน์จากคุณสมบัตินี้ ทำให้เราสามารถส่งฟังก์ชันเข้าไปในฟังก์ชันเพื่อเลือกพฤติกรรมที่ต้องการได้ ตัวอย่างฟังก์ชันที่ถูกออกแบบมาในลักษณะนี้ได้แก่

ผมขอยก merge-with ขึ้นมาเป็นตัวอย่างเพื่ออธิบาย
 
Clojure มีฟังก์ชัน merge ที่ใช้รวม map มากกว่า 2 อันเข้าด้วยกัน โดยหากมี key ที่ซ้ำกันระหว่าง map จะทำการใช้ value จาก map อันสุดท้าย

แต่การเลือกเอาค่าสุดท้ายไม่ได้เป็นสิ่งที่เราต้องการเสมอไป Clojure จึงมีฟังก์ชัน merge-with ให้เราเลือกใช้หากเราต้องการพฤติกรรมเมื่อมี key ซ้ำกันที่แตกต่างออกไปจาก merge ปกติ เช่น
เราสามารถเลือกรวม vector เช่นนี้ได้โดยใช้ concat ช่วย

(merge-with concat {:a [1 2 3]} {:a [4 5 6]})
;=> {:a (1 2 3 4 5 6)}
 หรือจะเลือกเอาค่าที่มากที่สุดโดยการส่ง max เข้าไป
 
(merge-with max {:a 1} {:a 3} {:a 2}) 
;=> {:a 3} 
นอกจากจะมีคู่ merge & merge-with แล้ว ฟังก์ชันอื่นๆ ด้านบนก็มีคู่ในลักษณะเดียวกัน partition & partition-by, split & split-with, assoc & update, assoc-in & update-in, sort & sort-by

จะเห็นได้ว่าการเขียนโปรแกรมในลักษณะนี้ ทำให้เกิดโค้ดที่มีพฤติกรรมใหม่ๆ ได้มากมายไม่จำกัด โดยเราแทบจะไม่ต้องเขียนอะไรเพิ่มขึ้นเลย ผมว่านี่แหละเป็น code reuse ที่เราโปรแกรมเมอร์พยายามทำให้เกิดในโปรแกรมที่เราเขียน และนี่เป็นหนึ่งในสาเหตุที่ทำให้โปรแกรมที่เขียนในลักษณะ functional สามารถสั้นกว่าโปรแกรมที่เขียนในลักษณะอื่นได้ ด้วยการทำให้เกิดการรวมกันของฟังก์ชัน (composability) เพื่อเกิดเป็นโปรแกรมที่มีพฤติกรรมใหม่ๆ ตามต้องการได้ง่าย

หวังว่าตัวอย่างเหล่านี้จะช่วยเพิ่มความเข้าใจในประโยชน์ของ functional programming มากขึ้นนะครับ