Signatures That Don't Lie

Share
Signatures That Don't Lie

เคยไหมครับ อ่านชื่อฟังก์ชันในโค้ด เห็น Input และ Output ดูปกติมาก แต่พอรันจริงกลับเจอ Runtime Error พ่นใส่หน้า หรือเจอ Null โผล่มาแบบไม่ได้รับเชิญ ทั้งที่ Type ไม่ได้บอกไว้...

ในโลกของ Functional Programming (FP) เรามีเป้าหมายอย่างหนึ่งคือการเขียน "Honest Functions" หรือฟังก์ชันที่มี Signature "ซื่อสัตย์" ต่อผู้ใช้ วันนี้เราจะมาเจาะลึกกันว่าแนวคิดนี้คืออะไร และทำไมมันถึงช่วยให้เรานอนหลับเต็มอิ่มขึ้นครับ 🥹

ฟังก์ชันที่ "โกหก" (Dishonest Functions) คืออะไร?

ฟังก์ชันที่โกหก คือฟังก์ชันที่ Signature (สิ่งที่มันประกาศ) กับ Behavior (สิ่งที่มันทำจริง) ไม่ตรงกัน โดยส่วนใหญ่มักจะโกหกเราใน 3 (+1 ในบางภาษา) รูปแบบหลัก:

1. Partial Functions

มันบอกว่ารับ number นะ แต่พอยัดเลข 0 เข้าไป กลับระเบิดตัวเองตาย (Crash) เพราะมันไม่ได้รองรับเลขศูนย์จริงๆ ตั้งแต่แรก แถมสัญญาว่าจะ return number แต่ดัน throw exception

2. Hidden Side Effects

มันบอกว่าคืนค่า string แต่ลึกๆ แอบไปลบไฟล์ในเครื่อง หรือไปเปลี่ยนค่าตัวแปร Global โดยที่เราไม่รู้

3. การใช้ Exceptions ในการจัดการ Business Logic

การโยน Exception ใน Functions ที่ใช้จัดการ Business ต่างๆ ก็ถือเป็นการโกหก เพราะมันคือการข้ามลำดับการทำงานปกติ (Non-local GOTO) และทำให้ผู้เรียกไม่ทราบว่าฟังก์ชันอาจจะล้มเหลวได้หากดูเพียงแค่ Signature

4. การใช้ Nulls (ในบางภาษา)

ในบางภาษา ประเภทข้อมูลอ้างอิงอาจเป็นค่าว่าง (null) ได้เสมอ ทำให้ Signature ที่ระบุว่าจะส่งกลับ Object กลายเป็นการโกหก เพราะบางครั้งมันส่งกลับ null แทน

แล้วฟังก์ชันที่ "ไม่โกหก" (Honest Functions) คืออะไร?

มันคือฟังก์ชันที่มีคุณสมบัติตรงข้ามกับฟังก์ชันที่โกหก ผ่ามๆ หยอกๆ หลักๆคือควรจะมีคุณสมบัติดังนี้:

1. บอกอินพุตที่เป็นไปได้ทั้งหมด

Signature ต้องระบุอย่างชัดเจนว่ารับค่าอะไรได้บ้าง และจะไม่มีการทำงานผิดพลาดหากส่งค่าตามประเภทที่ระบุมา

2. บอกผลลัพธ์ที่เป็นไปได้ทั้งหมด

ไม่ว่าผลลัพธ์จะสำเร็จหรือล้มเหลว (เช่น Error หรือค่าว่าง) ข้อมูลเหล่านี้ต้องถูกระบุไว้ในประเภทข้อมูลที่ส่งกลับ (Return Type)

3. No Hidden Side Effects

ฟังก์ชันต้องไม่แอบไปแก้ไขค่า Global หรือติดต่อฐานข้อมูลโดยที่ไม่ได้ระบุไว้ใน Signature ซึ่งจะช่วยให้เกิด Referential Transparency (ไม่อธิบายใน blog นี้นะ 🤣) หรือความสามารถในการแทนที่ฟังก์ชันด้วยค่าของมันได้โดยไม่ทำให้พฤติกรรมของโปรแกรมเปลี่ยนไป

เทคนิคการเบื้องต้นในการช่วยให้เราออกแบบ Honest Functions

1. การใช้ Wrapper Types

แทนที่จะส่งกลับค่าตรง ๆ ให้ใช้ประเภทข้อมูลอย่าง Maybe, Option หรือ Either เพื่อระบุชัดเจนว่าผลลัพธ์อาจจะไม่มีค่าหรือเกิดข้อผิดพลาดได้

2. การจำกัดประเภทอินพุต (Restrict Input Types)

เช่น แทนที่จะรับ int สำหรับตัวหาร ให้สร้างประเภทข้อมูลใหม่ชื่อ NonZeroInteger เพื่อให้ Compiler บังคับว่าห้ามส่งค่า 0 มาตั้งแต่ต้น

3. เน้น Immutability และ Pure Functions

การใช้ข้อมูลที่เปลี่ยนแปลงไม่ได้ช่วยให้เรามั่นใจว่าฟังก์ชันจะทำงานแค่ตามที่ระบุใน Signature เท่านั้น

จริงๆมีเทคนิคอีกมากมายในการทำให้ Function ของเรา "ซื่อสัตย์" มากขึ้นลองไปศึกษากันเพิ่มดูนะ

เปรียบเทียบ Honest Functions กับ Dishonest Functions

ลองมาดูตัวอย่าง function สำหรับหารเลข

function divide(a: number, b: number): number {
  if (b === 0) throw new Error("หารด้วยศูนย์ไม่ได้นะจ๊ะ!");
  return a / b;
}

ปัญหา: function นี้ ดูผ่านๆ ถ้าคนที่เอาฟังก์ชันนี้ไปใช้ไม่ได้เข้ามาดู "ไม่มีทางรู้เลย" ว่าต้องครอบ try-catch เว้นแต่จะเข้าไปอ่าน Code ข้างในหรือรอให้มันพังกลางอากาศ

ถ้าจะแก้ให้เป็น Honest Functions โดยส่วนมากเราจะใช้ระบบ Type มาเพื่อช่วยบอกความจริงทั้งหมด ในตัวอย่างจะสร้าง Type ใหม่เป็น Sum Type ที่ชื่อว่า Result มา โดยที่ Result จะเป็นได้ทั้ง Success และ Error ดังตัวอย่างด้านล่างนี้

type Result<T> = { status: 'success', value: T } | { status: 'error', message: string };

/**
 * Signature นี้บอกความจริง 100%: 
 * "ฉันรับ number นะ แต่อาจจะคืนเป็นผลลัพธ์ หรือข้อความ Error ก็ได้"
 */
function safeDivide(a: number, b: number): Result<number> {
  if (b === 0) {
    return { status: 'error', message: "Division by zero" };
  }
  return { status: 'success', value: a / b };
}

ข้อดี: ในบางภาษา Compiler จะบังคับให้คุณจัดการกรณี Error ทันที คุณลืมไม่ได้เพราะ Type มันค้ำคออยู่

ทำไมผมถึงแคร์ ?

เมื่อเราเขียนโปรแกรมตามหลัก Signatures that don't lie ผลลัพธ์ที่ตามมาคือ:

  1. Code คือ Documentation: คุณไม่จำเป็นต้องเขียนคอมเมนต์อธิบายยาวเหยียดว่าฟังก์ชันจะพังตอนไหน เพราะ Type มันบอกไว้หมดแล้ว
  2. Refactoring อย่างมั่นใจ: เมื่อคุณเปลี่ยน Logic ระบบ Type จะฟ้องทันทีว่าจุดไหนที่คุณยังจัดการไม่ครบ
  3. ลด Cognitive Load: สมองไม่ต้องจำว่า "ฟังก์ชันนี้ห้ามส่งค่านี้เข้านะ" เพราะ Compiler จะเป็นคนจำแทนคุณเอง
  4. ลดความซับซ้อน (Reduce Complexity): คุณสามารถทำความเข้าใจฟังก์ชันได้จากการดูแค่ Signature โดยไม่ต้องไล่โค้ดทีละบรรทัด (Local Reasoning)

สรุปส่งท้าย

การเปลี่ยนมาเขียนฟังก์ชันที่ "ไม่โกหก" อาจจะดูยุ่งยาก (มาก) ในช่วงแรก เพราะเราต้องห่อหุ้มค่า (Wrap values) และจัดการเคสต่างๆ มากขึ้น แต่ในระยะยาว มันคือการลงทุนที่คุ้มค่า เพราะมันเปลี่ยนจาก "การไล่ตามแก้ Bug" มาเป็นการ "ออกแบบระบบที่ไม่เปิดโอกาสให้เกิด Bug" ตั้งแต่แรกครับ

"Be honest with your types, and your code will be honest with you."

Happy Coding ครับ

Read more

Distributed Decision Making

Distributed Decision Making

ในองค์กรใหญ่ ๆ อะไร ๆ ก็ช้าลงไปหมด และสิ่งที่ช้าที่สุดคือการตัดสินใจนี่แหละครับ การตัดสินใจกระจายอยู่ในทุก ๆ อณูขององค์กร องค์กรที่มีบรรยากาศสบาย ๆ ทุกคนรู้สึกปลอดภัย ใคร ๆ ก็จะกล้าตัดสินใจและกล้าออกความเห็น แต่พอองค์กรใหญ่

By Chokchai Phatharamalai
Risk Management: The Hard Test

Risk Management: The Hard Test

ท้ายหนังสือ Slack: Getting Past Burnout, Busywork, and the Myth of Total Efficiency ของ Tom DeMarco ได้ให้เทคนิคใหม่ในการจัดการความเสี่ยงกับผม ทอมสอนว่าในการทำงานยุคปัจจุบัน งานมีความเสี่ยงกระจายอยู่เต็มไปหมด ซึ่งในความเสี่ยงนั้น เรามีโอกาสโชคดีและมีโอกาสโชคร้าย การจัดการความเสี่ยงเป็นสิ

By Chokchai Phatharamalai
วิธีปรับเวลาการนอน

วิธีปรับเวลาการนอน

ผมเดินทางกลับมาจาก Conference ของ Berkeley ที่ซานฟรานซิสโก เครื่องลงที่สนามบินสุวรรณภูมิประมาณ 22:30 น. กว่าจะถึงบ้านก็เกือบเที่ยงคืน ยังดีที่ขากลับไม่เหนื่อยเท่าขาไป เพราะลองซื้อหมอนรองคอจาก Duty Free ที่ซานฟรานซิสโกมาใช้ดู หมอนเป็นลายการ์ตูน มีรูปสะพาน

By Chokchai Phatharamalai
เร็วแค่ไหนก็ไร้ค่า ถ้าไปผิดทาง

เร็วแค่ไหนก็ไร้ค่า ถ้าไปผิดทาง

อีกบทเรียนที่ผมได้จากหนังสือ Slack: Getting Past Burnout, Busywork, and the Myth of Total Efficiency ของ Tom DeMarco คือ ทำไมองค์กรใหญ่ ๆ ถึงยึดมั่นกับ Efficiency กันนัก Efficiency คืออะไร? Efficiency แปลว่า "ประสิทธิภาพ" ยกตัวอย่างเช่น

By Chokchai Phatharamalai