Say hello to Clojure

Arm Pacino
Technologies For Everyone
2 min readFeb 24, 2017

Clojure เป็นภาษาระดับสูงลิ่วภาษาหนึ่งซึ่งรันอยู่บน Java Virtual Machine ไวยกรณ์ของมันแทบไม่มีอะไรนอกจากวงเล็บเปิดตามด้วยชื่อฟังก์ชันกับอาร์กิวเมนต์แล้วก็วงเล็บปิด ลองดูตัวอย่างคำสั่งของมันสักหน่อย

(apply * (range 1 6))

output ของคำสั่งข้างบนคือ 120
คำสั่ง (range 1 6) นี้จะสร้าง List ที่มีสมาชิกดังนี้ออกมา (1 2 3 4 5) แล้วคำสั่ง apply ก็เอาไปทำงานต่อโดยเอา * หรือเครื่องหมายคูณไป operate กับสมาชิกทุกตัวใน List ผลที่ได้ก็คือ 1 x 2 x 3 x 4 x 5 = 120 สรุปว่าคำสั่งนี้มันหาค่า factorial นั่นเอง

ถ้าเขียนฟังก์ชัน factorial ใน C แบบง่ายสุดก็คงใช้ for loop อาจจะประกาศตัวแแปร sum ไว้เก็บผลลัพธ์ และประกาศตัวแปร i เพื่อใช้วนลูป ต้องกำหนดเงื่อนไขด้วยว่าถ้า i > n แล้วให้ออกลูป แล้วก็ต้องบอกให้มัน +1 ทุกๆลูปด้วย แล้วในลูปก็สั่งให้ sum คูณกับ i ไปเรื่อยๆ เมื่อรันจบก็จะได้ sum เป็นผลลัพธ์

กว่าจะได้ factorial ใน C จะเห็นว่าเราต้องกำหนดอะไรเยอะแยะ โอเค คุณอาจคิดว่าก็ไอ้แค่การประกาศตัวแปรกับเขียน for loop มันจะไปยากตรงไหน ผมไม่เถียงครับ แต่ลองนึกย้อนกลับไปตอนคุณเพิ่งเรียน C ใหม่ๆ คุณใช่เวลาแค่ไหนกว่าจะเข้าใจการประกาศตัวแปรและการเขียน for loop ? เรื่องเล็กๆพวกนี้ทำให้คนที่เรียน Com-Pro หลายคนท้อแท้แล้วก็เกลียดการเขียนโปรแกรมไปซะงั้น ซึ่งเป็นเรื่องน่าเสียดายครับ ถ้าให้พวกเขาลองเขียน Clojure พวกเขาอาจจะชอบก็ได้ เพราะมีแต่ความสนุกล้วนๆ

Clojure ออกแบบมาเพื่อเขียนโปรแกรมแบบ Functionl Programming คือไม่มีการ Assign ค่าตัวแปรใดๆทั้งสิ้น มีแต่การเรียกใช้ฟังก์ชันเท่านั้น อ่าวแล้วจะเขียนโปรแกรมได้หรอ? ได้ครับ ถ้าคุณเขียน C หรือ Java แปลว่าคุณเขียนโปรแกรมตามทฤษฎีของ Alan Turing คือเปลี่ยน state ของโปรแกรมไปเรื่อยๆที่ละขั้นตอน เสร็จแล้วจึงใช้ state เป็นผลลัพธ ์แต่การเขียนโปรแกรมแบบ Functional จะเป็นไปตามทฤษฏีของ Alonzo Church (เขาเป็น supervisor ของ Alan Turing เชียวนะ) คือเรียกใช้ฟังก์ชันซ้อนฟังก์ชันไปเรื่อยๆแล้วเอาค่าที่ return เป็นผลลัพธ์ มีการพิสูตรแล้วว่าโปรแกรมใดๆที่เขียนแบบ Alan Turing ได้ ก็จะเขียนแบบ Alonzo Church ได้เหมือนกัน (Church–Turing thesis)

เช่น จะเขียนโปรแกรมบวกเลข 1 ถึง n ใน C ก็วนลูปเอาแบบที่ทุกคนเข้าใจ แต่ในแบบ Functional จะเขียนลูปไม่ได้ เพราะลูปต้องมีการเซ็ตค่า i ซึ่งการเซ็ตค่าผิดหลักของ Functional จึงต้องเขียน recursive ได้ประมาณนี้

int sum(int n){
if(n==0) return 0;
else return n+sum(n-1);
}

“อย่างนี้มันก็ช้าน่ะสิ?” ช้าครับถ้าเขียนในภาษาพวก C หรือ Java แต่ถ้าเขียนในภาษาแบบ Functional โดยเฉพาะ Compiler จะมีวิธี Optimize ของมัน
“คือมันเขียนยากอ่ะ ใน C ก็เขียนง่ายๆ แล้วจะเขียนแบบนี้ไปทำไมหรอ?” การเขียนแบบ Functional มีประโยชน์ของมันอยู่ครับ

  1. Debug ง่ายกว่า
    เมื่อเซ็ตตัวแปรไม่ได้ก็หมายความว่าไปเซ็ตค่าใน Global ไม่ได้ ไปเซ็ตค่าในฟังก์ชันอื่นก็ไม่ได้ แต่ละฟังก์ชันจึงเป็นอิสระต่อกัน จะไม่มีกรณีที่ฟังก์ชัน A เผลอไปเซ็ตค่าใน Global แล้วฟังก์ชัน B เอาไปใช้ต่อทำให้ผลลัพธ์ของ B เพี้ยน ถ้าเกิด bug แบบนี้เราต้องไล่ดูทุกฟังก์ชันเลยว่ามีฟังก์ชันไหนทะลึ่งไปเซ็ตค่าที่ทำให้ B เพี้ยนบ้าง การไล่ดู bug แบบนี้ในโค้ดขนาดใหญ่ไม่สนุกแน่ แต่ถ้าเกิด bug ในโค้ดแบบ Functional เราจะรู้ว่า bug อยู่ใน scope ของตัวมันเองแน่นอน หา bug ง่ายขึ้นเยอะ
  2. Development Cycle สั้นกว่า(มาก) ภาษาแบบ Functional จะมีเครื่องมือที่เรียกว่า REPL (Read-Eval-Print Loop) เครื่องมือนี้ใช้ทดสอบฟังก์ชันที่เขียนขึ้นได้เลยโดยไม่ต้อง compile โค้ดใหม่ เราจึงเขียนโปรแกรมไปทดสอบไปได้อย่างสายฟ้าแลบ (หรือจะทดสอบแค่ส่วนย่อยๆในฟังก์ชันก็ยังได้)
  3. รันแบบ Parallel ได้สมบูรณ์กว่า วิชา Com-Architect บอกแล้วว่าถ้ารันโค้ดหลายๆ instruction พร้อมกัน จะมีบางกรณีที่มันจะเซ็ต state เพี้ยนๆและ compiler ก็ต้องเพิ่ม no-op เข้าไปเพื่อเพื่อรักษา state ไว้ โค้ดแบบ Turing จึงรันแบบ Parallel ได้ไม่สมบูรณ์ แต่ใน Functional แต่ละฟังก์ชันมันทำงานใครทำงานมัน จึงรันแบบ Parallel ได้เต็มที่

The Hello is said

Clojure ยังมีฟังก์ชันขี้โกงอีกมากมายให้เรามาเล่นสนุกกัน หนึ่งในนั้นคือการเขียน Macro หรือโค้ดที่เขียนโค้ดนั่นเอง เราหวังว่าทุกคนจะได้อะไรจากการเขียนโค้ดแบบ Functional ไปบ้างถึงแม้จะกลับไปเขียน C หรือ Java ก็ตาม ท้ายที่สุดจงสนุกกับ Clojure ในบทความตอนต่อไปเราจะสอน set editor กับ environment กัน

--

--