เริ่มต้นกับ Functional Programming

Share
เริ่มต้นกับ Functional Programming

ก่อนหน้านี้ผมเจอโพสต์หนึ่งถามว่า สำหรับคนที่เคยใช้แต่ OOP จะเริ่มต้นกับ Functional Programming ควรเริ่มยังไงดี

ผมชอบคำตอบที่ตัวเองตอบไป เลยอยากเอามาจดเก็บไว้

ฝันร้ายของโปรแกรมเมอร์

ก่อนจะเข้าไปใน programming แต่ละ paradigm ผมอยากเริ่มต้นที่ปัญหาก่อน

หนึ่งในปัญหาที่หลอกหลอนโปรแกรมเมอร์มากที่สุด คือ บั๊กบน production ที่เกิดจาก shared mutable state โดยเฉพาะ global variable เพราะ global variable คือตัวแปรที่โค้ดหลายส่วนของโปรแกรมสามารถเข้าถึงและแก้ไขได้ นั่นแปลว่า ถ้าค่าของมันผิดขึ้นมา โค้ดที่เป็นผู้ต้องสงสัยอาจกระจายอยู่ทั่วทั้งระบบ

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

ถ้ามองผ่านปัญหา shared mutable state นี้ OOP และ FP เป็นสองแนวทางที่จัดการกับปัญหาคล้าย ๆ กัน แต่ใช้วิธีต่างกัน

OOP แก้ปัญหายังไง?

ถ้ากลับไปดูภาษาโปรแกรมมิ่งเก่า ๆ อย่าง assembly ซึ่งเป็น imperative programming เราจะเห็นการเขียนโปรแกรมในรูปแบบชุดคำสั่งเพื่อสั่งงานเครื่องคอมพิวเตอร์ เช่น เอาตัวเลขนี้ไปเก็บในช่องนั้น ย้ายค่าจากช่องนั้นไปช่องนี้ หรือเอาค่าจาก 2 ช่องมาบวกกัน เป็นต้น

เมื่อโปรแกรมใหญ่ขึ้น ถ้า state ต่าง ๆ ถูกเปิดให้โค้ดทุกส่วนเข้าถึงและแก้ไขได้ การดูแลโปรแกรมก็จะยากขึ้นเรื่อย ๆ

หนึ่งในแนวคิดสำคัญของ Object-Oriented Programming คือการเอา state กับ behavior ที่เกี่ยวข้องกันมาอยู่ในขอบเขตเดียวกัน แล้วซ่อนรายละเอียดภายในเอาไว้

เช่น ถ้าเรามี Object Student ข้อมูลต่าง ๆ ของ Student ไม่ว่าจะเป็นชื่อ เกรดเฉลี่ย หรือชั้นปี ก็อยู่กับ Student และพวกฟังก์ชันที่ใช้จัดการข้อมูลเหล่านี้ก็อยู่กับ Student เช่นกัน เราเรียกฟังก์ชันเหล่านี้ว่า method

แทนที่โค้ดทั้งระบบจะเข้ามาแก้ข้อมูลของ Student โดยตรง เราสามารถกำหนดได้ว่าใครเห็นอะไรได้ และจะเปลี่ยน state ผ่านทางไหน

พอเราจำกัด scope และซ่อน state ไว้ ถ้า Student มีค่าอะไรบางอย่างผิดพลาด ผู้ต้องสงสัยก็แคบลง เพราะโค้ดภายนอกไม่สามารถเข้ามาแก้ state ภายในได้ตามใจ

พูดง่าย ๆ คือ OOP ไม่ได้พยายามกำจัด state ที่เปลี่ยนแปลงได้ แต่พยายามควบคุมว่า ใครมีสิทธิ์เข้าถึงและเปลี่ยน state นั้น

นอกจาก scope แล้ว Object ยังมี life cycle ของตัวเองด้วย เมื่อ Object นั้นไม่ถูกใช้งานแล้ว ระบบก็สามารถจัดการ memory ที่เกี่ยวข้องกับมันได้

FP แก้ปัญหายังไง?

ถ้า OOP พยายามควบคุมว่าใครแก้ state ได้บ้าง Functional Programming ไปอีกทาง คือพยายามลดการแก้ state ลงให้มากที่สุดตั้งแต่แรก

ถ้าจะพูดเล่น ๆ แบบสุดโต่งหน่อย คือ

OOP พยายามแบน global
FP พยายามแบนการเปลี่ยนค่า variable

Functional Programming ให้ความสำคัญกับ immutability คือ เมื่อข้อมูลถูกสร้างขึ้นมาแล้ว เราพยายามไม่แก้ค่าของมัน แต่สร้างค่าใหม่จากค่าเดิมแทน

เช่น ถ้ามี

x = 1

แทนที่จะเปลี่ยน x จาก 1 เป็น 2 เราจะสร้างค่าใหม่ขึ้นมา

x = 1
y = x + 1

x ก็ยังคงเป็น 1 เหมือนเดิม

อีกแนวคิดสำคัญคือ pure function ซึ่งเป็น function ที่ผลลัพธ์ขึ้นอยู่กับ input ที่รับเข้ามา ถ้า input เหมือนเดิม ก็ควรให้ output เหมือนเดิม และไม่แอบไปเปลี่ยนแปลง state ข้างนอก function

ลองนึกภาพว่าเรามี

input
  ↓
function
  ↓
output

สิ่งที่เกิดขึ้นใน function สามารถทำความเข้าใจได้จาก input และ output ของมัน โดยไม่ต้องตามไปดูว่า global variable ตัวไหนเพิ่งถูกแก้มาก่อนหน้านี้

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

ผลที่ตามมาคือ code ส่วนหนึ่งของเราจะ predictable มากขึ้น และเหตุผลเกี่ยวกับมันได้ง่ายขึ้น

FP กับการย้อนเวลา

ผลพลอยได้ที่น่าสนใจมากของ immutable data คือ มันทำให้การย้อนกลับไปดู state ในอดีตง่ายขึ้นมาก

สมมติว่าโปรแกรมมี state แบบนี้

state1
  ↓
state2
  ↓
state3

ถ้าเราใช้ mutable state เราอาจมี Object เดียวที่ถูกแก้ไปเรื่อย ๆ

state
  ↓ mutate
state
  ↓ mutate
state

ค่าเก่าถูกเขียนทับไปแล้ว ถ้าอยากรู้ว่าเมื่อก่อนมันเป็นอะไร เราต้องมี mechanism เพิ่ม เช่น log, snapshot หรือ undo history

แต่ถ้าเราใช้ immutable data เราสามารถสร้าง state ใหม่จาก state เดิมได้

state1 = ...
state2 = update(state1)
state3 = update(state2)

โดย state1 และ state2 ยังอยู่เหมือนเดิม

ดังนั้นถ้าเราอยากย้อนกลับไปดูว่า ตอนนั้นโปรแกรมอยู่ใน state ไหน เราก็สามารถกลับไปดู state เก่าได้

นี่คือเหตุผลว่าทำไมระบบที่ใช้แนวคิด Functional Programming บางประเภทถึงทำสิ่งอย่าง time-travel debugging ได้ง่าย

มันไม่ได้ย้อน program pointer กลับไปในอดีตจริง ๆ แต่มันสามารถย้อนกลับไปดู state ในอดีต ได้ เพราะ state เก่าไม่ได้ถูกทำลายทิ้ง

ทำไมผมถึงไม่กลัว function ยาวใน FP เท่า method ยาวใน OOP

ความเรียบง่ายแบบนี้ทำให้ผมไม่กลัว function ยาว ๆ ใน FP เท่ากับ method ยาว ๆ ที่เต็มไปด้วย mutable state

ไม่ใช่ว่า function ยาวเป็นเรื่องดีนะ ยาวก็ควรแยกอยู่ดี

แต่ถ้า function หนึ่งเป็น pure function ผมสามารถดูมันแบบ

input → transformation → output

แล้วพยายามทำความเข้าใจมันได้โดยไม่ต้องกังวลมากนักว่า ระหว่างทางมันไปเปลี่ยน state อะไรข้างนอกหรือเปล่า

ในทางกลับกัน method ยาว ๆ ที่เปลี่ยน state ของ Object ไปเรื่อย ๆ จะทำให้ผมต้องจำตลอดว่า ตอนนี้ค่าของแต่ละ field เปลี่ยนไปเป็นอะไรแล้ว

อันนั้นสำหรับผมน่าสยดสยองกว่าเยอะ

ทำไม FP ถึงยากสำหรับคนที่ชินกับ Imperative Programming

สิ่งหนึ่งที่ทำให้ผมรู้สึกว่า FP ยากในตอนเริ่มต้น คือ วิธีคิดของมันต่างจาก imperative programming พอสมควร

เวลาเขียน imperative เรามักจะคิดเป็นขั้นตอนว่า

ทำ A
แก้ค่า B
วน loop
ถ้าเป็นแบบนี้ให้แก้ C
แล้วค่อยไปทำ D

แต่เวลาเขียน FP ผมมักจะคิดในรูปของการเปลี่ยนข้อมูลจากรูปหนึ่งไปอีกรูปหนึ่งมากกว่า

เช่น ถ้าผมมีคำตอบของข้อสอบทุกวิชาของนักเรียนคนหนึ่ง แล้วอยากหาเกรดเฉลี่ย ผมอาจเริ่มถามตัวเองว่า

Q: ก่อนจะได้เกรดเฉลี่ย จะต้องมีอะไรก่อน?
A: เกรดทุกวิชา

ผมก็จะรู้ว่า function สุดท้ายที่ return เกรดเฉลี่ย จะต้องรับ input เป็นเกรดของทุกวิชา

แล้วถามต่อว่า

Q: ก่อนจะได้เกรดทุกวิชา ต้องมีอะไรก่อน?
A: คะแนนสอบของแต่ละวิชา

ก็จะได้ transformation ประมาณนี้

คำตอบของข้อสอบ
    ↓
คะแนนสอบ
    ↓
เกรด
    ↓
เกรดเฉลี่ย

สุดท้ายโปรแกรมก็จะกลายเป็นการต่อ function เล็ก ๆ เข้าด้วยกัน

input
  → transform
  → transform
  → transform
  → output

วิธีคิดแบบนี้ไม่ใช่ว่า imperative programming ทำไม่ได้ หรือ FP ต้องคิดถอยหลังเสมอไป

แต่มันเป็นวิธีคิดแบบ data transformation และ function composition ซึ่งผมเจอบ่อยมากเวลาเขียน Functional Programming และสำหรับคนที่ชินกับ imperative programming อาจต้องใช้เวลาปรับวิธีคิดอยู่บ้าง

ทำไมควรรู้ทั้ง FP และ OOP

วิธีการแก้ปัญหาของ 2 paradigm นี้ มันเหมือนค้อนกับกรรไกร บางงานมันแก้ด้วยเครื่องมืออีกอย่างง่ายกว่ามาก ๆ ถ้าเรารู้จักทั้ง 2 เครื่องมือ เราก็จะเลือกใช้ได้อย่างเหมาะสม

OOP พยายามทำให้ state ที่เปลี่ยนแปลงได้อยู่ในขอบเขตที่ควบคุมได้

FP พยายามลดจำนวน state ที่เปลี่ยนแปลงได้ลงตั้งแต่แรก

ทั้งสองแนวทางไม่ได้เป็นศัตรูกันด้วย ทุกวันนี้ภาษาโปรแกรมจำนวนมากก็เอาแนวคิดจากทั้ง OOP และ FP มาใช้ร่วมกัน

ผมเคยเห็นโค้ด imperative เป็นหลายพันบรรทัด ที่พอเปลี่ยนมามองปัญหาเป็น data transformation แล้ว ใช้แนวคิดแบบ FP กลับเหลือโค้ดไม่ถึง 200 บรรทัดก็มี

ที่ผมเลือกเอาเรื่องนี้มาแบ่งปัน เพราะในชีวิตเราอาจจะเจอปัญหาที่บางครั้งเราคิดว่ายากจังเลย

ผมอยากฝากคำของ Gerald Weinberg ไว้ว่า

ปัญหาไม่มีง่ายหรือยาก ยากหรือง่ายมันอยู่ที่ solution ในการแก้ปัญหา
ปัญหาที่เราคิดว่ายาก คือปัญหาที่เรายังไม่รู้ solution ง่าย ๆ

Read more

Values ของ Framework ต่าง ๆ

Values ของ Framework ต่าง ๆ

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

By Chokchai Phatharamalai
Accepted Responsibility

Accepted Responsibility

ช่วงนี้ผมกำลังอ่านหนังสือ Extreme Programming Explained ของ Kent Beck แล้วก็ได้เจอหัวข้อหนึ่งที่กระทบใจ คือ principle ที่เรียกว่า Accepted Responsibility Principle คืออะไร? ในหนังสืออธิบายว่า Principle คือหลักการที่ทำให้ Values ของ XP กลายเป็น Practices ได้

By Chokchai Phatharamalai
The Five Ideals

The Five Ideals

จากหนังสือ The Unicorn Project ในนั้นจะมีที่ปรึกษาเก่ง ๆ คนหนึ่งชื่อ Erik เป็นเหมือนเซ็นเซหาตัวยาก เดี๋ยวก็มา เดี๋ยวก็ไป ถ้าโชคดีก็จะเจอเค้าว่าง แล้วเค้าก็จะสอนอะไรที่สำคัญ ๆ แต่ฟังดูล้ำ ๆ และลึกซึ้ง

By Chokchai Phatharamalai
Communication, Reliability and Modern concepts - System Design, Simply Explained

Communication, Reliability and Modern concepts - System Design, Simply Explained

บทความนี้จะอธิบายพื้นฐานของ System Design โดยต่อยอดจากตัวอย่าง Gym Application เพื่อให้เห็นภาพว่า เมื่อระบบเริ่มเติบโตและถูกแยกออกเป็นหลาย Service แต่ละ Service จะสื่อสารและทำงานร่วมกันอย่างไร งานแบบไหนควรสื่อสารแบบ Synchronous งานแบบไหนควรประมวลผลแบบ Asynchronous รวมถึงอธิบาย Concept พื้นฐานต่าง ๆ ที่ควรรู้ เช่น API

By Boonsong Srithong