มาเรียนรู้พื้นฐาน Functional Programming กัน
/ 10 min read
สามารถดู video ของหัวข้อนี้ก่อนได้ ดู video
Functional Programming คืออะไร ?
Functional Programming คือ รูปแบบวิธีคิดการเขียน program รูปแบบหนึ่ง โดยหยิบไอเดียมาจากการทำ function คณิตศาสตร์ที่จะไม่เกี่ยวข้องกับตัวของ state ตัวอื่นๆนอกเหนือจาก function เป็นเพียงการนำข้อมูลใส่ function และให้ได้ผลลัพธ์ออกมาเพียงเท่านั้น
ไอเดียใหญ่ๆของ Functional Programming คือ การรับข้อมูลเข้ามาจัดการข้อมูลและส่งผลลัพธ์ของ function ออกมา “โดยไม่เปลี่ยนแปลงข้อมูลต้นฉบับหรือตัวของโปรแกรม” ถ้าเราจะเปรียบเทียบให้เข้าใจง่าย เปรียบเทียบเหมือนการทำงานเป็นลำดับขั้นตอนแต่ละขั้นตอนแยกออกจากกัน เหมือนการชงกาแฟที่ต้องมี step ของ
- ต้มน้ำ
- บดกาแฟ
- เทน้ำร้อนลงบนกาแฟกด
ซึ่งทั้ง 3 ขั้นตอนนี้ มีหน้าที่แยกออกจากกันชัดเจน ต้มน้ำก็มีหน้าที่ทำให้น้ำเดือด บดกาแฟก็แค่ทำให้เมล็ดกาแฟกลายเป็นผง และ เทน้ำร้อนลงกาแฟกด แค่เป็นการน้ำน้ำที่ร้อนแล้วมาเทใส่ผงกาแฟ เพื่อให้กลายเป็นกาแฟออกมาได้ ซึ่งทั้ง 3 อันนี้สามารถทำแยกออกจากกันได้ และไม่มีการเปลี่ยนแปลงสูตรของ 3 ขั้นตอนนั้น (ต้มน้ำก็ต้มเหมือนเดิม บดกาแฟก็บดเหมือนเดิม) ซึ่งนี่คือการแยก function ออกจากกันและการให้ผลลัพธ์เหมือนเดิมเสมอ (เหมือนกับ Functional Programming นั่นเอง)
Ref: https://www.researchgate.net/figure/lxgc-reduction-graph-for-lylzzyx_fig1_228386201
พื้นฐานของ Functional Programming นั้นจริงๆแล้วมาจากพื้นฐานของ Lambda calculus
Lambda calculus นั้นถูกคิดค้นโดยนักคณิตศาสตร์ Alonzo Church ในช่วงปี 1930 ซึ่งเป็นสิ่งที่วางรากฐานสำคัญของการทำสิ่งที่เรียกว่า “function” ในคณิตศาสตร์เลยก็ว่าได้ Lambda calculus คือการพยายามจินตนาการโลกว่า “ทุกอย่างในโลกนี้คือ function” ไม่ว่าจะเป็นตัวเลข, operator, ข้อมูลใดๆ หรือ function อื่นๆ เป็น function ทั้งหมด โดยการใส่ input เข้ามา ใส่สิ่งนี้เรียกว่า “function” นี้ ก็จะสามารถได้ output ของ function ที่เรามีการนิยมออกมาได้ (ใช่ครับ มันคือการนิยาม function ออกมานั่นแหละ)
- ใน Lambda calculus จะใช้สัญลักษณ์ λ (lambda) เป็นการกำหนด anonymous functions โดยสามารถกำหนด parameter เป็นเหมือน input ของข้อมูลที่เราจะจัดการด้วย และผลลัพธ์ที่ออกมาจาก lambda + parameter = ผลลัพธ์ของการทำงาน function ออกมาได้
- Lambda calculus คือ framework เชิง concept ที่ช่วยทำให้เราสามารถสร้างสิ่งที่เรียกว่า function ออกมาในรูปแบบคณิตศาสตร์ออกมาได้ (เหมือนกับภาพด้านบน) เหมือนกับการกำหนดภาษาในการคุย function ออกมา
- โดย idea หลักของ function นั้นนอกเหนือจากการทำงานภายในและส่งข้อมูลออกมาได้ ยังสามารถทำงานต่อกันจาก function ตัวอื่นๆไปต่อได้ ส่งผลทำให้เราสามารถสร้าง function ที่ซับซ้อนขึ้น จากการซ้อน function ออกมาได้
ซึ่งสิ่งนี้เป็นต้นแบบของการมอง function ว่าเป็น “purely functional” คือ function จะต้องทำงานตามจุดประสงค์ที่ใส่ไป และต้องไม่ถูกรบกวนจากสิ่งอื่นจนได้ผลลัพธ์ออกมา เพื่อส่งผลทำให้ function สามารถได้ผลลัพธ์ออกมาเหมือนเดิมทุกรอบตามนิยามของคณิตศาสตร์ออกมา ซึ่งสิ่งนี้พอมาอยู่ในโลกของการเขียน programming จึงกลายเป็นข้อดีที่ส่งผลทำให้ function (ในทาง program) สามารถที่จะทำงานตามจุดประสงค์ที่กำหนดเท่านั้น และง่ายต่อความเข้าใจต่อตัว code ดวยเช่นเดียวกัน
- รวมถึง idea ของ Higher-order functions ที่เป็นพื้นฐานสำคัญของ Functional Programming ซึ่งสามารถรับ function เป็น input และส่งออกเป็น output ออกมาได้ ก็เป็นพื้นฐานมาจาก Lambda calculus เช่นกัน
และนี่คือสิ่งที่เรียกกันว่า Functional Programming เดี๋ยวเราจะเริ่มมาเจาะลึกกันเชิงของ program บ้างว่า เราสามารถนำมาประยุกต์ใช้กับการเขียนโปรแกรมยังไงได้บ้าง
ไอเดียการวางระบบแตกต่างกับ OOP ยังไง ?
แน่นอน เชื่อว่าหลายๆคนน่าจะรู้จักการเขียนโปรแกรมเชิงวัตถุอย่าง Object-oriented programming (OOP) ซึ่งก็ถือเป็นรูปแบบการเขียนโปรแกรมรูปแบบหนึ่งเช่นเดียวกันกับ Functional Programming ซึ่งทั้ง 2 อย่างนี้มีจุดแข็งและจุดพิจารณา รวมถึงมุมมองต่างกันพอสมควร เราจะมาลองเล่าให้ฟังกันทีละจุดแบบนี้นะครับ
- Concept
- OOP focus การมองของออกเป็น object ทำการห่อคุณสมบัติทุกอย่างไว้ภายใน object ไว้ (เช่น รถสีแดง วิ่งได้ความเร็วสูงสุด 120 km/h) และพยายามเชื่อมโยง object ทุกอย่างเข้าหากัน ด้วยสิ่งที่เรียกว่า พฤติกรรม (behavior) (เช่น รถสีแดงถูกใช้โดยคุณหมอและกำลังพยายามขับรถไปยังสถานที่ปลายทาง)
- Functional Programming คือการเปลี่ยน “ทุกอย่าง” เป็น function ออกมา เหมือนคณิตศาสตร์ โดยพยายามนำเสนอทุกอย่างต้องเกิดจากการใส่ input และได้ผลลัพธ์เป็น ouput ออกมาเสมอ โดยจะต้องไม่มีการแก้ไขหรือแทรกแทรงอะไรระหว่างดำเนินการ
มันจะเหมือนกับภาพนี้ เวลาที่เราพูดถึง function คณิตศาสตร์ มันก็คือการใส่ input และได้ output ของ function นั้นออกมา และก็จะเป็นแบบนี้เสมอ ตราบเท่าที่ใช้ function ตัวเดิมออกมา
Ref: https://media.geeksforgeeks.org/wp-content/uploads/20231119175537/Domain-and-Range.png
- Data กับ state
- OOP data ทั้งหมดจะโดนเก็บไว้ object และถูกแก้ไขจาก method ของ object นั้นๆ โดยจะอนุญาตให้เกิด “mutable state” ได้เนื่องจาก สิ่งที่เรียกว่า state นั้นจะถูกเก็บไว้ใน object นั้นอยู่แล้ว (เช่น Bank Account ที่เราสามารถฝากเงินหรือถอนเงินเพิ่มได้ ซึ่งจำนวนเงินที่เป็น state ก็จะเก็บไว้อยู่ภายใน object นั้น)
Ref: https://stackoverflow.com/questions/53395244/class-diagram-of-a-bank-confusion
- Functional Programming data ทั้งหมดจะไม่สามารถ access หรือโดนดัดแปลงได้จากภายใน function ดังนั้น การจัดการ data จึงต้องเกิดจากการส่งต่อจาก function สู่ function ออกมาเท่านั้น จะไม่ได้มีการเก็บ state ไว้ใน function แต่จะเป็นแค่การนำข้อมูล (ที่เป็น state เริ่มต้น) ไปจัดการผ่าน function และรับผลลัพธ์ตัวใหม่ออกมาแค่นั้น
- Control Flow
- OOP ทำงานเหมือนการจัดการเขียนโปรแกรมเป็นลำดับขั้นตอน (Imperative Programming) ใช้ loop control flow เป็น step by step เหมือนเขียนโปรแกรมแบบปกติ
- Functional Programming เป็นการเขียน program เชิงผลลัพธ์ (Declarative Programming) โดยจะเป็นการ function ที่เราต้องการออกมา และส่งต่อสิ่งที่เราต้องการนั้น เข้า function อื่นๆต่อเข้าไป เพื่อให้ได้ผลลัพธ์ออกมา (เป็นการประกอบ function ของ function ให้ได้ผลลัพธ์ที่ต้องการออกมา แทนที่จะเขียนเป็น step by step ตามปกติ)
นี่คือ 3 ไอเดียใหญ่ๆ ที่มีความแตกต่างจะเห็นว่า มุมมองการเขียนโปรแกรมทั้ง 2 แบบแตกต่างกันอย่างสิ้นเชิง
- OOP มองไปสิ่ง state ของสิ่งที่เรากำลังทำอยู่ และพยายามจัดการกับ state ของสิ่งที่เราสร้างมา
- แต่ Funtional programming มองแค่การสร้าง “function ของจุดประสงค์” และนำ function เหล่านั้นมาประกอบกันเป็นผลลัพธ์ที่เราต้องการออกมา
ดังนั้น 2 Idea นี้หากเลือกใช้รูปแบบตัวนั้น ก็ต้องใช้ตัวนั้นทั้ง program เพื่อป้องกันการสับสนจากมุมมองของการ design program ด้วยเช่นกัน เดี๋ยวเราจะลองมาแชร์การเขียน program ฉบับ Functional Programming style เทียบกับการเขียนแบบ OOP (ในเคสง่ายๆ) กันว่า มุมมอง code 2 ตัวนี้แตกต่างกันอย่างไร ผ่านภาษา Javascript กัน (ขอเลือกเป็นภาษานี้เพราะเป็นภาษาที่อ่านง่ายและ Support ความเป็น functional จากหลายๆ function ของ Javascript อยู่แล้วเช่นกัน)
มารู้จักเทคนิค functional แต่ละแบบ
ผมจะแบ่งการเล่าออกเป็น 2 ส่วนคือ ส่วนที่เป็นแกนหลักที่มีความแตกต่างระหว่่าง Functional กับ การเขียน program แบบ imparative ทั่วไป (ที่อาจจะมี OOP บางเคสบ้าง) กับ เทคนิคเพิ่มเติมของ Functional เมื่อ function เริ่มมีความซับซ้อนมากขึ้น เพื่อให้เห็นภาพการเขียน program แบบ functional มากขึ้น
Pure function
คุณสมบัติสำคัญอันแรกของ Functional Programming คือ ต้องเป็น “pure function” เสมอ โดย concept หลักของ “pure function” คือ
- input เหมือนเดิม ต้องได้ output เหมือนเดิม “เสมอ”
- ต้องไม่มีใครสามารถแก้ไข state ระหว่างกลางของ function ได้ (เช่นการใช้ global variable, การดึง API ข้างนอก)
- เราสามารถแทน function นี้ตรงตำแหน่งใดๆก็ได้ที่ใช้ “output ตัวเดียวกัน” ได้ (output เหมือนเดิมเสมอ)
นี่คือตัวอย่างของ Impure function (ที่ไม่ตรงตาม practice ของ Functional Programming)
สังเกตว่า
incrementCounter()
นั้นไม่ได้มีการรับ input อะไรเข้าไป แต่ทุกครั้งที่รับค่ามาใหม่ จะได้รับค่าเพิ่มขึ้นเสมอ เนื่องจากมีการใช้ตัวแปรcounter
ที่เป็น Global variable อยู่- จึงส่งผลให้ทุกรอบที่ run function นี้ได้ผลลัพธ์ที่ไม่เหมือนเดิมออกมา จาก
counter
ที่เพิ่มขึ้นในแต่ละรอบนั่นเอง
และนี่คือ Pure function
สังเกตว่า function add(a, b)
จะทำหน้าที่เพียงอย่างเดียวคือการบวกตัวเลข a + b
เสมอดังนั้นไม่ว่าเราจะ run function นี้กี่รอบ output ก็จะได้เหมือนเดิมและมีหน้าที่เพียงแค่อย่างเดียว
Declarative style
Declarative style ใน Functional Programming คือการอธิบายส่วนของ function ออกมาเป็น “ผลลัพธ์” แทนที่จะเป็นการจัดการ code แบบ step by step Declarative เป็นการเรียก function การใช้งานตามจุดประสงค์ออกมาแทน (Declarative เป็นการ focus สิ่งที่เราต้องการทำให้เสร็จที่อยู่ระดับ high-level)
เพื่อให้เห็นภาพมากขึน เราจะลองมาดู code ตัวอย่างกัน สมมุติว่า โจทย์เราคือ “สร้าง array ใหม่ที่เอาเฉพาะตัวที่หาร 2 ลงตัว”
ถ้าเป็นการเขียน program ตามแบบ imparative style code ก็จะออกมาตรงไปตรงมาตามโจทย์เลยแบบนี้
ซึ่งการเขียนแบบนี้ เราก็จะเป็นการเขียนตาม step by step เลย ต้องมี loop ที่ใช้สำหรับการวนแต่ละตัวและ condition สำหรับการแยกเคสออกมา
แต่ถ้าเป็นการเขียน program ตามแบบ declarative style เราจะทำการ
- เพิ่ม function สำหรับการหาตัวเลขหาร 2 ลงตัวออกมา และคืนผลลัพธ์ออกมา
- เมื่อรับผลลัพธ์มาใหม่ก็จะสามารถนำผลลัพธ์จาก function นั้นออกมาใส่ตัวแปรใหม่ได้
- โดยตัว code นั้นเราจะไม่มีการใช้ loop หรือ condition โดยตรง (เป็นการใช้งานผ่าน function
.filter()
ออกมาแทน)
ซึ่งสิ่งนี้ก็จะยังคงคุณสมบัติของ pure function เอาไว้ (ไม่ว่าจะ run กี่รอบก็จะได้ผลลัพธ์นี้ออกมาได้)
High order function
High-order functions (ขอย่อเป็น HOFs) คือ function ที่สามารถ “นำ function มาเป็น input” (หรือ arguments function) ได้ หรือ สามารถ return ค่าออกมาเป็น function ในฐานะ output ออกมาได้ เพื่อใช้คุณสมบัติของ “pure function” ให้สามารถ reuse ต่อกันได้
เรามาลองทำ 2 โจทย์นี้คือ “เพิ่มตัวเลขใน array เป็น 2 เท่า” และ “หาเลขคู่จาก array”
หากเราเขียนโดยไม่ใช้ HOFs ก็จะได้ code ลักษณะแบบนี้ออกมา (code ตรงไปตรงมาคือ วน loop > สร้าง condition และนำไปใส่อีก Array หนึ่งออกมา)
ซึ่งเมื่อเราเขียนเป็น HOFs ก็จะมีลักษณะเป็นแบบนี้ออกมาแทน
อย่างที่เราเห็น ทั้ง .map()
และ .filter()
ได้ทำการใส่ argument ตัวหนึ่งเข้าไปนั่นคือ function ของการทำงานนั้นๆ โดย
.map()
function ที่เราใส่เป็น argument คือ function สำหรับการคูณตัวเลขที่ใส่เป็น parameter ใน function ไป ทำให้เมื่อใช้คำสั่ง.map()
คำสั่งนี้ก็จะทำ function ส่งเข้าไปในข้อมูลแต่ละตัวและทำการ return ค่าใหม่จากการใช้ function argument นั้นเข้าไปได้นั่นเอง- เช่นเดียวกันกับ case ของ
.filter()
ที่เราใส่เป็น argument คือ function สำหรับการหาเลขหาร 2 ลงตัวเข้าไปแทน
ซึ่งใน Javascript นั้นมี function ที่ช่วยทำให้ Javascript นั้นสามารถใช้คุณสมบัติของ Functional Programming ไว้ได้อย่าง .map()
, .filter()
รวมถึง reduce()
(ใช้สำหรับการรวมผล), sort()
(ใช้สำหรับการเรียง) เช่นแบบนี้
ซึ่งถือเป็นตัวที่ช่วยอำนวยความสะดวกในการเขียน Functional Programming ของ Javascript เช่นเดียวกัน (จริงๆยังมีคำสั่งอีกหลายตัวที่ใช้งานได้นะครับ สามารถ search keyword High order function javascript เพิ่มเติมได้ครับ)
หรืออีกตัวอย่างหนึ่งในกรณีที่เราไม่ได้ใช้งานร่วมกับ array หรือ object เราก็สามารถส่ง function เข้าไปทำงานตรงๆได้เช่นเดียวกัน เช่นแบบนี้
จาก code นี้
- เรามี
repeat(n, fn)
ที่รับ 2 ค่าคือ n = จำนวนครั้งที่จะทำ และfn()
คือ function ที่เราจะทำงานซ้ำออกมา - เมื่อเราสร้าง function ใหม่
logMessage()
ออกมา เราก็จะสามารถส่ง function นั้นเป็น argument ใหม่ ผ่านrepeat(3, logMessage())
ออกมาได้ และใน repeat ก็จะทำงานใช้งานlogMessage()
ผ่านfn()
ที่เป็นตัวแทนของ Parameter ออกมาได้
มา Step up กันอีกสักหน่อย
จาก 3 ข้อเมื่อมันคือแกนหลักของการทำ functional (เอาจริงๆ ใครที่ get idea ของทั้ง 3 อัน สามารถทำ functional มาต่อยอดกันได้แล้ว) ทีนี้ เราจะมาแนะนำ technique อื่นๆเพิ่มเติมกัน
Composition
Composition คือ concept ของ Functional Programming ที่จะเพิ่มความสามารถในการ “รวม function” เข้าด้วยกันได้ โดยการรวม function ที่ทำงานแบบเรียบง่ายหลายๆ function เข้าด้วยกันจนกลายเป็น “complex function” ออกมาได้ ลักษณะเหมือนการนำ output ของอีก function หนึ่งส่งต่อไปเป็น input ของอีก function หนึ่งออกมานั่นเอง
Ref: https://danielpecos.com/2014/06/24/function-composition/
เรามาลองดูผ่านตัวอย่างกัน สมมุติว่าเรามี 2 functions คือ
double(x)
คือ function สำหรับการเพิ่มตัวเลข 2 เท่าและ return ออกมาincrement(x)
คือ function สำหรับการเพิ่มค่าให้กับตัวเลขที่ input เข้าไป 1 และ return ออกมา
หน้าตา function เป็นแบบนี้
มาดูตัวอย่างแบบไม่ใช้ Composition กันก็จะเหมือนกับเราเขียน programming ทั่วๆไปเลย
- เรียกใช้ทีละ function ตั้งแต่
double(x)
,increment(x)
โดยincrement(x)
ก็จะนำผลลัพธ์จากตัวแปรตัวก่อนมาใส่และทำการส่งผลลัพธ์ออกไปได้
ทีนี้เมื่อลองใช้ Composition เราก็จะสามารถเขียนเป็นรูปแบบนี้ออกมาได้
- ให้
double(x)
ที่คำนวนได้นั้นตอนได้ output ออกมา ให้ไปเป็น input ของincrement(x)
ต่อ และทำการ return เป็น function ออกมา
สังเกตว่า ทั้ง 2 เคสได้ผลลัพธ์ออกมาเหมือนกันแต่ Function ของ Composition นั้นจะอ่านง่ายกว่า รวมถึงไม่จำเป็นต้องมีตัวแปรออกมาโดยไม่จำเป็นด้วย นี่จึงเป็นอีก 1 technique ของ functional ที่มักมีการใช้กัน
(จริงๆถ้าเราลองดูดีๆมันคือการใช้ความสามารถของ High order function นั่นแหละ)
Currying
Curry คือ เทคนิคของ Functional Programming ที่สามารถทำการแปลง function ที่ต้องรับทีละหลาย arguments ให้กลายเป็น sequence ของ function ที่รับ “ทีละ argument ออกมาได้”
โดยจาก concept ของตัวนี้นั้น เราจะทำการ break function จาก n argument มาสู่ n function (ที่รับทีละ 1 argument) และค่อยๆใช้งาน argument ทีละตัวผ่าน input function และส่งต่อ function พร้อมการรับ argument ต่อไปผ่าน output function ออกมาแทน
เพื่อให้เห็นภาพมากขึ้น เรามาดูผ่านตัวอย่างนี้กัน เรามากำหนดโจทย์กันว่า เราจะสร้าง function สำหรับการลดราคาสินค้าโดย
- สร้าง function
applyDiscount()
ที่สามารถรับราคาสินค้าและส่วนลดสินค้าเข้าไปได้ - ouput ที่ออกมาก็จะเป็นราคาหลังจากที่ลบ discount ตามที่รับออกไปแล้ว
หากไม่ได้ใช้เทค Curry function ทั่วไปก็จะมีหน้าตาประมาณนี้
หากใช้เทคนิค Curry สิ่งที่เราจะทำคือ
- สร้าง function ขึ้นมา 1 ตัวรับค่า
discount
เข้าไป - ทำการ return ออกมาเป็น function ที่รับ argument
price
ออกมา - เมื่อใครก็ตามที่ใช้งาน function ผ่าน output ตัวนี้ จะทำการใช้งาน
discount
ที่เป็น curry function ที่ผ่านการใส่ discount แล้วออกมา = ส่งผลทำให้ทุกคนที่เรียกใช้งานผ่าน curry function จะได้รับ discount ไปใช้งานได้เลย โดยไม่ต้องส่ง argument มาใหม่ทุกรอบ
หน้าตา code ก็จะเป็นประมาณนี้
นี่คือประโยชน์ของเทคนิค Curry มันจะสามารถ “reuse argument” ผ่านการส่งต่อเข้าแต่ละ function ออกมาได้
Partial
Partial เป็นอีก 1 เทคนิคของ Functional Programming ที่มี concept คือการลดจำนวน argument ลง โดย “เปลี่ยน argument หลายตัวเป็น function แทน” ซึ่ง function เหล่านั้นก็จะเป็นส่วนที่ทำตัวเหมือน “prefilled” ค่าของ argument เอาไว้ก่อน ซึ่งส่งผลทำให้ argument ของ function ลดลง และช่วยลดความซับซ้อนของ function ลงได้
ถ้าอ่านๆดู “มันเหมือนกับ Curry เลยนี่นา” ใช่ครับ พื้นฐานไอเดีย 2 ตัวนี้จะเหมือนกัน แต่จุดที่แตกต่างของ Partial คือ Partial จะสามารถ “รับ argument มากกว่า 1 ตัว”มาจัดการได้ แตกต่างกับ Curry ที่ต้องกระจายให้กลายเป็น function ละ 1 argument และส่งต่อไป
จุดประสงค์ของ Partial จึงเป็นเหมือนลดความซ้ำซ้อนของตัว function จากเทคนิคของ Curry ลงมากกว่า
ลองมาดูผ่านตัวอย่างกันครับ เช่น โจทย์คือ เราจะสร้าง function สำหรับ log ออกมาโดย log จะต้องส่งข้อมูลออกมาทั้งหมด 3 อย่างคือ
- level เป็น log ระดับไหน (Info, Warning, Error)
- message ข้อความที่จะแสดง log ออกมา
- timestamp เวลาที่มีการบันทึก log
หากเราใช้เทคนิค Curry ก็จะได้หน้าตาประมาณนี้ออกมา
แต่เมื่อใช้เทคนิค Partial จะได้หน้าตาแบบนี้ออกมาแทน
Key idea ใหญ่ๆที่แยกการใช้งานระหว่าง Curry และ Partial นั้นโดยปกติจะมี
- จำนวนของ argument ว่าจำนวนเยอะเกินกว่าที่จะนำ function มาซ้ำซ้อนกันหรือไม่
- ความยืดหยุ่น ว่าต้องการหรือต้องทำบางอย่างกับหลาย argument พร้อมกันหรือไม่ก่อนส่งต่อ (ถ้าไม่มีเหตุจำเป็นอย่างเคสนี้ เอาจริงๆก็สามารถใช้ Curry ได้)
มาดูตัวอย่าง use case โดยประมาณกัน
ตอนนี้เรารู้จัก Functional Programming กันโดยประมาณและ (เอาจริงไอเดียหลักๆของ Functional Programming มันก็ประมาณนี้แหละ) มาเรียนรู้ผ่านตัวอย่างเพิ่มเติมกัน โดยเราจะลองเทียบกันระหว่าง OOP กับ functional เพื่อเติมเต็มความเข้าใจที่มากขึ้น
เราจะลองมาทำโจทย์นี้กัน
ตีโจทย์แบบ OOP
ใน OOP นั้นเราจะ focus เป็น class ออกมา ดังนั้นสิ่งที่เราจะทำคือ
- สร้าง class
User
และ methoddoubleAge()
คู่กับ user เอาไว้ เพื่อให้สามารถ คูณ 2 อายุของ User ได้ - และในแต่ละขั้นของโจทย์ เราก็จะค่อยๆทำตามลำดับไปเลย
สังเกตจาก code นี้
- ตรงข้อที่ 2 นั้นจะมีการเปลี่ยนแปลงค่าภายในของ object ด้วยคำสั่ง
doubleAge()
ส่งผลทำให้ ตัวแปร age ที่อยู่ใน object ไม่เหมือนเดิม หลังผ่านคำสั่งนี้ไป - สำหรับ OOP นี่คือการเก็บสิ่งที่เรียกว่า “คุณสมบัติ” คู่กับ object เอาไว้ แต่มันก็จะแลกมาด้วยการยอมให้ state สามารถแก้ไขตัวมันเองได้
ตีโจทย์แบบ Functional Programming
สิ่งที่เราจะทำคือ เมื่อมี 3 โจทย์ที่เราต้องทำ = ก็ต้องมี “3 function” ที่ใช้สำหรับตอบโจทย์นั้นออกมานั่นคือ
doubleAge()
สำหรับการเพิ่มอายุ 2 เท่าsumAges()
สำหรับการรวมอายุfilter()
สำหรับการคัดกรองคนเฉพาะ isActive ออกมา
เมื่อเรานำทุก function มาใช้งานร่วมกัน ก็จะได้ลักษณะ code ออกมาเป็นแบบนี้
สังเกตจาก code นี้
- ทุก function จะเป็นเพียงแค่การรับ argument ของ user เข้ามาแค่นั้น จะไม่มีการเปลี่ยนแปลงข้อมูลอะไรของ user เลย
- ทุก function จะทำงานต่อกันผ่าน output ของ function ที่ส่งออกมา (ใน javascript การใช้ function chain ต่อกัน โดยการใช้
.
= เป็นการนำ output ไปสู่ function ต่อไป) - ดังนั้น แม้จะ run ทุก function ไป
users
ที่เป็นต้นฉบับข้อมูลก็จะยังคงเหมือนเดิม ไม่ได้มีการเปลี่ยนแปลงอะไรจากการ run function ที่เกิดขึ้น ตามคุณสมบัติ “pure function” ของ Functional Programming นั่นเอง
สรุป
จากที่บทความเล่ามา Functional Programming คือ 1 ในรูปแบบวิธีคิดการเขียน Programming อีกหนึ่งรูปแบบ ที่เป็นการปรับไอเดียจากการคิด program ตามลำดับ (imparative) เป็นการคิด program ตามจุดประสงค์และผลลัพธ์ (declarative) ออกมาแทน เหมือนการมองโลกทุกอย่างเป็น function ออกมา ตามไอเดียของ Lambda Calculus
ไม่มีข้อสรุปใดๆว่า Functional Programming หรือ OOP นั้นไอเดียไหนดีกว่ากัน ทั้งนี้จะขึ้นอยู่กับว่า “มุมมองไหน ส่งผลทำให้ระบบ design ออกมาง่ายและ Test ออกมาง่ายมากกว่ากัน” ตัวนั้นก็จะเหมาะสมกับงานนั้นๆกว่า
จากประสบการณ์ของผมเอง
- OOP จะเหมาะกับเคสที่เล่นกับ Data structure จำนวนเยอะมาก (เช่น พวกระบบหลังบ้านที่ต้องจัดการกับ Database) จะส่งผลทำให้จัดการ code ได้ง่ายกว่า
- Functional Programming นั้น จะเหมาะกับเคสที่ต้องการสร้าง cover test ให้ครบทุก function รวมถึงเคสของ concurrency หรือเคสอะไรก็ตามที่ต้องนำ function กลับมาใช้ซ้ำบ่อยๆ (เช่น ระบบจัดการแบบ realtime, ระบบ data pipeline ที่ต้องส่งต่อข้อมูลไปมาหากัน) จะส่งผลทำให้ maintain ได้ง่ายขึ้นมากกว่า
แต่อย่างที่ย้ำไปครับ “ไม่มีคำตอบตายตัวสำหรับเรื่องนี้” ดังนั้น จะ Functional Programming หรือ OOP ก็ล้วนเป็น Idea ทาง programming ที่ดีทั้งคู่ หากปรับใช้อย่างเหมาะสมและตรงตามหลักการของทั้ง 2 วิธีนี้ อย่างน้อยที่สุดก็ส่งผลทำให้ code เราอ่านได้ง่ายขึ้น และ maintain ได้ง่ายขึ้นอย่างแน่นอน ลองศึกษา use case พิจารณาใช้กันตามความเหมาะสมกันด้วยนะครับ
- แนะนำ Dynamic programming แบบนิ่มนวลที่สุดมี Video
บทความนี้จะแนะนำเบื้องต้นเกี่ยวกับ Dynamic programming เทคนิคหนึ่งที่ใช้สำหรับแก้ปัญหาที่ ปัญหาย่อยที่ทับซ้อนกัน (overlapping subproblem)
- Code Quality & Security with SonarQubeมี Video
แนะนำพื้นฐานการทำ Sonarqube Code พร้อม Code Quality & Security
- รู้จักกับ React Hook และ Componentมี Video
พาทัวร์ feature ต่างๆของ React กันแบบรวดเร็วกัน สำรวจไปทุกๆ feature พร้อมกัน
- Random บน Computer สุ่มแบบไหนเราถึงเรียกว่าสุ่ม ?มี Video
เคยสงสัยกันไหมครับ เวลาที่เราทอยลูกเต๋า สุ่มหยิบการ์ดออกจากกอง หรือแม้แต่สุ่มโดยการทอยเหรียญ สิ่งนี้เมื่อย้ายเข้าไปทำงานอยู่บนคอมพิวเตอร์มันทำงานยังไง