Monday, April 21, 2014

ยังไงถึงจะเป็น functional programming language

พอดีในกลุ่ม THJUG ใน Facebook มีการพูดคุยกันเกี่ยวกับ feature ใหม่ของ Java 8 หนึ่งในเรื่องที่ถูกพูดถึงบ่อยมากที่สุดเรื่อง Lambda ซึ่งนี่เป็นครั้งแรกของ Java ที่ developer สามารถส่ง function ไปมาได้โดยไม่ต้องสร้าง interface แล้ว implement ให้วุ่นวาย

หลายคนที่เขียน Java เดิมอยู่อาจสงสัยว่า ไอ้ Lambda นี่มันมีอะไรดี ทำไมมันดูฮิตติดลมบนกันจังช่วงนี้ ทำไมเค้าว่ากันว่าไอ้เจ้านี่มันทำให้ Java รองรับสไตล์การเขียนแบบ "Functional"

ตอนแรกผมกะจะเขียนเรื่องข้อดีของความเป็น functional แต่ขอเปลี่ยนใจเล่าเรื่องเกี่ยวกับนิยามของมันก่อน เพราะเดี๋ยวเวลาพูดถึงข้อดี อาจจะต้องอ้างอิงด้วยว่า ข้อดีแต่ละข้อ ฟังดูดีเวอร์ แต่มันจะเป็นไปได้ยังไง

นิยามของคำว่า functional ... เราจะแยก programming language ออกได้ยังไงว่าอะไรคือ functional อะไรคือไม่ใช่ นิยามส่วนตัวของผมเอง (ไม่อิงหลักวิชาการ) ภาษาจะเป็น functional ได้ ต้องมีคุณสมบัติหลัก ๆ ดังนี้

  1. function เป็น first class citizen แปลว่าอะไรที่ variable, value, object ฯลฯ ทำได้ function ต้องทำได้ด้วย เช่น เราส่งตัวแปร เข้า method นี้ แล้ว method return ตัวแปรอีกตัวออกมา  function ก็ต้องทำอย่างนั้นได้ด้วย เราต้องสร้าง function แล้วส่งมันเข้า method ให้ method คำนวณแล้ว return function ใหม่ออกมาได้ หรือต้องจับ function ยัดใส่ list ใส่ map ได้
  2. immutable data structures แปลว่า ภาษาต้องมีโครงสร้างข้อมูลที่การันตีว่าจะไม่ถูกแก้ไขค่าเตรียมไว้ให้ใช้ หรือถ้าไม่มีก็ต้องสามารถ implement ใช้เองได้โดยง่าย ด้วย syntax ที่ไม่เยิ่นเย้อ อาจจะนึกภาพไม่ออก ตัวอย่างเช่น array list ของ Java อันนี้ไม่ functional เพราะเป็น mutable data structure เราส่ง array list เข้าไปใน method ลึกลับ เราไม่มีโอกาสรู้เลยว่า list ของเราจะถูกแก้ไขค่าหรือไม่ ต่างกับภาษา functional ที่จะมี list แบบที่สามารถการันตีว่ามันจะไม่ถูกแก้ไขค่าแน่นอน ไม่ว่าจะผ่าน function ลึกลับกี่ครั้งก็ตาม
  3. partial application ผมก็ไม่ค่อยแม่นนิยามความแตกต่างกับ currying แต่ผมว่าไม่ค่อยสำคัญ ขอแปลเป็นภาษาบ้าน ๆ ว่ามันคือการเรียก function แบบใส่ parameter ไม่ครบ แล้วได้ function ใหม่ออกมา ถ้าเราใส่ parameter ให้ครบ มันก็จะ return ค่าให้เรา เช่น function "plus" (บวก) ซึ่งรับ parameter 2 ตัว ถ้าเราใช้มันในภาษาปกติ เราต้องเรียก plus(2, 3) มันจะคืน 5 ให้เรา ถ้าเราเรียก plus(2) จะเจอ compilation error แต่ในภาษา functional เราสามารถเรียก plusWith2 = plus(2) แล้วตามด้วย result = plusWith2(3) ได้ โดย plusWith2 เป็น function ใหม่ที่ถูกสร้างกลางอากาศ
  4. lazy evaluation เอาไว้เวลาเราต้องการนิยามค่าบางอย่างที่ยาวไม่สิ้นสุด เช่น ลำดับ fibonacci หรือ list ที่มีค่า [0,1,2,3,0,1,2,3,0,1,2,3, ... ไปเรื่อย ๆ]
  5. pattern matching เปรียบเหมือน switch/case ที่เก่งกว่ามาก ๆ อธิบายไม่ถูก ดูตัวอย่างเลยดีกว่า
1.| simpleZipWith :: (a → b → c)[a][b][c]
2.| simpleZipWith _ [] _ = []
3.| simpleZipWith _ _ [] = []
4.| simpleZipWith ƒ (a:as) (b:bs) = ƒ a b : simpleZipWith f as bs
หน้าตาอาจจะดูแปลก ๆ มั่ว ๆ ซักหน่อยถ้าใครไม่เคยเห็นภาษานี้ ผมใส่สีไว้เพื่อให้รู้ว่าอะไรคืออะไร เจ้า simpleZipWith เป็น function ที่รับ parameter 3 ตัว ได้แก่ function, list ตัวแรก และ list ตัวที่สอง โดยมันจะหยิบ element แรกของ list ทั้งสองตัวมาผ่าน function แล้วเอาไปลง list ใหม่ ไปเรื่อย ๆ จนกว่า list ตัวใดตัวหนึ่งจะหมด มันก็จะหยุด
บรรทัดแรก ไม่มีอะไร เป็นการประกาศ type ของ function เฉย ๆ คิดซะว่า (a -> b -> c) แปลว่า function ที่รับ parameter 2 ตัว (a และ b) แล้ว return ค่านึงออกมา (c) ส่วน [a] [b] [c] แปลว่า list
บรรทัดที่ 2 บอกว่า parameter ตัวแรก (function) และตัวที่ 3 (list) เป็นอะไรไม่สนใจ ('_' แปลว่า "ไม่สน") ถ้า parameter ตัวที่ 2 เป็น list ว่าง ([] คือ element ใน list หมดแล้ว) ให้ return list ว่าง
บรรทัดที่ 3 บอกว่า parameter ตัวแรก กับ ตัวที่สอง เป็นอะไรไม่สน ถ้าตัวที่ 3 เป็น list ว่าง ให้ return list ว่าง
บรรทัดที่ 4 บอกว่า ถ้าไม่ใช่ทั้งกรณีในบรรทัดที่ 2 และบรรทัดที่ 3 ให้ถอดหัว list ตัวแรก (a) และ list ตัวที่สอง (b) ออกมา แล้วเรียก f(a, b) ได้ผลอะไรก็ตาม วางเอาไว้หน้า list แล้วขยับไปทำกับ list ส่วนที่เหลือ (as, bs) ด้วยหลักการเดียวกันไปเรื่อย ๆ (recursion) จน list ตัวใดตัวหนึ่งหมด โปรแกรมก็จะตกเคส บรรทัดที่ 2 หรือ 3 แล้วก็จะหยุด

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

1 comment:

  1. ผมยังไม่แม่นเรื่อง pattern matching เลยเรื่องอื่น ๆ พอจะเข้าใจคราว ๆ แล้ว กราบส์ (-/\-

    ReplyDelete