9 คะแนน โดย bboydart91 2026-02-08 | 2 ความคิดเห็น | แชร์ทาง WhatsApp

เนื้อหา

  • อธิบายกระบวนการไล่จากปัญหาสองอย่างที่แก้ไม่ได้ด้วย map ของฟังก์เตอร์เพียงอย่างเดียว (ปัญหาที่ฟังก์ชันติดอยู่ในคอนเทนเนอร์ และปัญหาการซ้อนของบริบทเมื่อทำการ compose) ไปจนถึง applicative functor และ monad ด้วยโค้ด TypeScript
  • เริ่มจากภูมิหลังในปี 1988 ที่ Eugenio Moggi สร้างแบบจำลองโปรแกรมเป็น A → T(B) แทนที่จะเป็น A → B
  • กล่าวถึงโครงสร้าง flatMap = map + join และกฎ 3 ข้อเพื่อใช้งานมันอย่างปลอดภัย (associativity, left identity, right identity)
  • อธิบายว่าทำไม monad จึงเป็น "วัตถุโมนอยด์ในหมวดเอนโดฟังก์เตอร์" โดยเทียบกับโมนอยด์ของการบวกจำนวนเต็ม
  • กล่าวด้วยว่าเหตุใด Promise จึงทำงานแบบ monadic แต่ไม่ใช่ monad เชิงคณิตศาสตร์อย่างเคร่งครัด

ข้อจำกัดของฟังก์เตอร์: สิ่งที่ map ทำไม่ได้

  • เมื่อใช้ map กับฟังก์ชันแบบ curried ผลลัพธ์จะกลายเป็น Maybe<(b: number) => number> ทำให้ฟังก์ชันติดอยู่ในคอนเทนเนอร์
    • map รับได้เฉพาะฟังก์ชันที่อยู่นอกคอนเทนเนอร์ จึงไม่มีวิธีนำฟังก์ชันที่ติดอยู่ข้างในไปใช้กับค่าอื่น
  • เมื่อ compose ฟังก์ชันสองตัวที่คืนค่าฟังก์เตอร์ จะเกิดการซ้อนของบริบทแบบ Maybe<Maybe>
    • ยิ่งเพิ่มจำนวนขั้น ก็จะซ้อนแบบไม่สิ้นสุดเป็น Maybe<Maybe<Maybe<...>>>

แอปพลิเคทีฟฟังก์เตอร์: การนำฟังก์ชันในคอนเทนเนอร์ไปใช้

  • ใช้โอเปอเรชัน apply เพื่อเอาฟังก์ชันที่ติดอยู่ในคอนเทนเนอร์ไปใช้กับค่าจากอีกคอนเทนเนอร์หนึ่งได้
    • apply: T<(A → B)> → T<A> → T<B>
  • ใช้โอเปอเรชัน pure เพื่อใส่ค่าบริสุทธิ์เข้าไปในคอนเทนเนอร์
  • ข้อจำกัด: ต้องกำหนดล่วงหน้าว่าจะ compose คอนเทนเนอร์ใดบ้าง
    • ไม่สามารถแสดงการพึ่งพาแบบลำดับเชิงพลวัตที่ตัดสินการคำนวณถัดไปจากผลลัพธ์ก่อนหน้าได้

Monad: การคิดค้นโอเปอเรชันสำหรับคลี่การซ้อน

  • โอเปอเรชัน join จะคลี่คอนเทนเนอร์สองชั้นจาก T<T<A>> ให้เหลือ T<A>
    • Array.prototype.flat ของ JavaScript ทำหน้าที่เดียวกัน
  • ในงานจริงมักใช้ flatMap ที่รวม map + join เข้าด้วยกัน
    • flatMap: T<A> → (A → T<B>) → T<B>
    • map รับ A → B แต่ flatMap รับ A → T<B> จึงคงผลลัพธ์ไว้เพียงชั้นเดียว

กฎสามข้อของ flatMap

  • กฎการประกอบเชื่อม: เมื่อคลี่ T(T(T(A))) ไม่ว่าจะคลี่จากชั้นในก่อน
    หรือจากชั้นนอกก่อน ผลลัพธ์ต้องเหมือนกัน
    • m.flatMap(f).flatMap(g) === m.flatMap(x => f(x).flatMap(g))
  • กฎเอกลักษณ์ซ้าย: ถ้าใส่ค่าด้วย pure แล้ว flatMap ทันที ต้องได้ผลเหมือนเรียกใช้ฟังก์ชันนั้นโดยตรง
    • pure(a).flatMap(f) === f(a)
  • กฎเอกลักษณ์ขวา: ถ้าส่ง pure ให้ flatMap ต้องได้คอนเทนเนอร์เดิมกลับมา
    • m.flatMap(pure) === m

การแยกความหมายของ "วัตถุโมนอยด์ในหมวดเอนโดฟังก์เตอร์"

  • ฟังก์เตอร์ในโปรแกรมมิงมุ่งจากโลกของชนิดข้อมูลไปยังโลกของชนิดข้อมูล จึงเป็นเอนโดฟังก์เตอร์
  • สามารถสร้างหมวดเอนโดฟังก์เตอร์ที่ใช้ตัวเอนโดฟังก์เตอร์เองเป็นวัตถุได้
  • เมื่อนำไปเทียบกับเงื่อนไขของโมนอยด์ (การดำเนินการทวิภาค + กฎการประกอบเชื่อม + เอกลักษณ์) จะได้ว่า:
    • การดำเนินการทวิภาค = join
    • เอกลักษณ์ = pure
    • โครงสร้างนี้สอดคล้องตรงกันพอดีกับโมนอยด์ของการบวกจำนวนเต็ม

เหตุผลที่ Promise ไม่ใช่ monad

  • then จัดการแบบผสมระหว่าง map และ flatMap ตามค่าที่คืนกลับ
  • สถานะแบบ Promise<Promise> ไม่ถูกอนุญาตในรันไทม์ และจะถูกรวมเป็นชั้นเดียวทันที
  • แม้จะสะดวกในทางปฏิบัติ แต่ก็ไม่สอดคล้องกับกฎ monad ทางคณิตศาสตร์

2 ความคิดเห็น

 
calofmijuck 2026-02-08

ช่วยพูดถึง Comonad ด้วยนะครับ!

 
bboydart91 2026-02-09

อืม...ขอคิดดูก่อนครับ 5555