Co-Product ในการเขียนโปรแกรม | Category Theory for Programming Part 2 | muitsfriday.dev

web-logo-doge muitsfriday.dev

Co-Product ในการเขียนโปรแกรม | Category Theory for Programming Part 2

Co-Product ในการเขียนโปรแกรม | Category Theory for Programming Part 2

Tue Feb 01 2022

จาก Part ที่แล้วเราได้รู้จักกับ Product ไครยังไม่อ่านคลิกๆ ในทฤษฎีแคตากอรี่จะมีการกระทำแบบนึงที่เราจะพบเจอได้บ่อย

โดยการกระทำนั้นต้องเริ่มจากเรามีโครงสร้าง/รูปแบบของแคตากอรี่ที่เราสนใจก่อน จากนั้นให้กลับลูกษร(morphism)เป็นทางตรงกันข้าม ในบางครั้งเราจะได้โครงสร้างที่มีคุณสมบัติน่าสนใจขึ้นมาด้วย

โครงสร้างที่ถูกสร้างขึ้นมาจากการสลับด้านมอฟิซึม เรามีชื่อเรียกให้มันล้ำๆ ว่า “duality” (ดูอัลลิตี้) ของโครงสร้างเดิม

จากบทความตอนที่ผ่านมา เราก็ได้รู้จักรูปแบบนึงที่เรียกว่า product(โปรดักต์) มาแล้ว เมื่อเราสลับด้านมอฟิซึม เราจะได้ดูอัลลิตี้ของโปรดักต์ โดยทั่วไปแล้วเรามักจะเรียกดูอัลลิตี้ของรูปแบบด้วยการเติม co- (โค) นำหน้าชื่อรูปแบบเดิม

ดูอัลลิตี้ของ product ก็คือ co-product เองจ๊ะ

Co-product 🎎

จากที่เกริ่นมาเบื่องต้น โครงสร้างแบบโคโปรดักต์จะมีหน้าตาแบบนี้

co-product in category

ทบทวนนิดนึง ในภาษาโปรแกรมเราจะโฟกัสที่ Category of Type ที่อ็อปเจ็กต์คือชนิดของค่า และมอฟิซึมคือฟังก์ชันที่แปลงชนิดของค่านึงไปเป็นอีกชนิดนึง

คราวนี้ความหมายของ co-product จากแผนภาพคือ มีอ็อปเจ็กต์(ชนิดของค่า) C ที่โดนแปลงมาจากอ็อปเจ็กต์ A หรือ B ได้

ในภาษาโปรแกรมเราจะรู้จักความสามารถนี้กันในนามของ union type หรือ either เป็นชนิดของค่าที่ถูกสร้างขึ้นมาจากขนิดอื่นที่มากกว่า 1 ตัวได้

เรามาดูตัวอย่างกันใน typescript กันดีกว่า ใน typescript เราสามารถเรียกใช้ union type ได้ด้วยการใช้เครื่องหมาย | คั่นระหว่าง 2 type ที่เราจะผสมเข้าไว้ด้วยกัน

type EitherNumberOrString = number | boolean

EitherNumberOrString เป็นชื่อเล่น(alias) ของ union type ที่รวมกันระหว่าง number และ boolean ตัว EitherNumberOrString เองเป็นได้ทั้ง number และ boolean

ถ้าหากอิงจากตามแผนภาพอ็อปเจ็กต์ A จะเป็น number ส่วน B ก็คืออ็อปเจ็กต์ boolean

ใน typescript ทั้งคู่สามารถแปลงเป็น EitherNumberOrString ได้ด้วยการ assign เข้าไปตรงๆ ไม่ต้องมี function อะไรช่วยพิเศษ

let m : EitherNumberOrString = true
let n : EitherNumberOrString = 10

ตรงนี้แม้จะไม่ได้ใช้ function แต่ก็มองว่าเป็นการ เปลี่ยนของ type ได้นั่นคือเป็นตัวแทนของเป็นมอฟิซึ่มในแผนภาพ

co-product in category

Nested Co-product 🔆

โปรดักต์เองยังซ้อนกันได้ ทำไมพี่น้องของโปรดักต์อย่าง โคโปรดักต์จะซ้อนบ้างไม่ได้ เราสามารถมองให้โครงสร้างแบบโคโปรดักต์ซ้อนกันเป็นชั้นลึกเข้าไปได้ดังภาพด้านล่างนี้

co-product in category

ไม่ว่าเราจะเอา co-product ไว้ด้านซ้ายหรือขวา ความหมายของ union ก็มีค่าเท่าเดิม (isomorphic กัน)

type FlexType = number | boolean | string

Sum type / Product type 📌

เรามีชื่อเรียกให้ type ที่ถูกสร้างจากโมเดล product ว่า product type และ co-product ว่า sum type แปลเป็นไทยตรงๆ ก็คือ ชนิดตัวแปลแบบคูณและแบบบวก ถึงตรงนี้อาจจะสงสัยแล้วว่าทำไมถึงได้ชื่อนี้ เราจะมาดูกันแต่ก่อนอื่นเพื่อเป็นตัวอย่างเราจะเริ่มจากให้มี type เริ่มต้นเอาไว้สองประเภทก่อนคือ (เป็นชนิดที่สมมติขึ้นมา)

  • A ค่าที่เป็นไปได้คือ ”{ a1, a2 }” สองค่านี้เท่านั้น (นึกภาพ)
  • B ค่าที่เป็นไปได้คือ ”{ b1, b2, b3 }” สามค่า

ตัวอย่างเราจะใช้ type สองอันนี้นะ

Product type

จากนั้นเราลองมาสร้าง type ที่เกิดจากการโปรดักต์กันก่อน

สมมติให้ C เป็น product type ที่เกิดจากการรวมของ A และ B จะได้ว่า C บรรจุค่าของ A และ B เอาไว้ในตัวจากบทความ part 1

interface C<A, B> {
  a: A;
  b: B;
}

ค่าที่เป็นไปได้ของ C จะมีได้ 6 ค่าตามตารางนี้

a1a2
b1(a1, b1)(a2, b1)
b2(a1, b2)(a2, b2)
b3(a1, b3)(a2, b3)

จะเห็นว่าค่าที่เป็นไปได้มีจำนวนเท่ากับ ค่าที่เป็นไปได้ทั้งหมดในชนิด A คูณกับ B (2 x 3 = 6) เป็นที่มาของชื่อ product type

Note: สำหรับคนที่สงสัยเรื่องความเกี่ยวข้องกับผลคูณคาทีเชียนของเซ็ต มันก็คือโครงสร้างโปรดักต์ของเซ็ตแคตากอรี่เช่นเดียวกันครับ

มาดูเรื่อง co-product กันต่อ สมมติให้ D เป็น type ที่เกิดจากการรวมแบบ co-product ของ A B หน้าตาของ D ก็จะเป็นแบบนี้

type D = A | B

ค่าที่เป็นไปได้ทั้งหมดของ D คือค่าของ A และ B รวมกัน คือ a1 a2 b1 b2 b3 ทั้งหมด 5 ตัว

จำนวนค่าที่เป็นไปได้เกิดจากจำนวน type ย่อยรวมกัน เป็นที่มาของชื่อ sum type

ช่วงถามตอบ ☕️

ตรงนี้จะรวบรวมคำถามที่เคยโดนถามมา และคำถามที่ผมเองคิดว่าทุกคนน่าจะสงสัยเอาไว้นะครับ

Q: ภาษาที่เป็น dynamic typing เช่น javascript จะไม่มี co-product หรือเปล่า

A: ไม่ใช่ครับ ให้เรามองว่าภาษานั้นตัวแปรที่รับส่งไปมาคือ type any ครับ ซึ่ง any คือ sum type ของทุกๆชนิดมาแล้วครับ

Q: ถ้า type A ค่าที่เป็นไปได้คือ x, y และ type B ค่าที่เป็นไปได้ คือ y, z แล้ว sum type ที่เกิดจากการรวมของ A B จำนวนค่าที่เป็นไปได้คือ 3 ไม่ใช่ 4 มันก็จะไม่ใช่ผลรวมแล้วหรือเปล่า?

A: จำนวนค่าที่เป็นไปได้คือ 4 ครับ เพราะทางทฤษฎี y ของ A และ y ของ B ถือเป็นคนละค่ากันครับ

Q: ถ้าภาษาไม่ซัพพอร์ตการสร้าง union type จะทำยังไงดี

A: เราสามารถใช้ท่าคล้ายๆแบบนี้ได้ครับ (อันนี้เป็น sudo code ที่พยายามทำให้ดูเหมือน java นะครับ)

class C {
  String type;
  Int a;
  Boolean b;
}

C myObject = new C();
myObject.type = "Int"
myObject.a = 10

C myObject = new C();
myObject.type = "Boolean"
myObject.b = true

อาจจะดูลำบากหน่อย แต่ตอนนี้ C เป็นได้ทั้ง int และ boolean เลยละครับด้วยการดู .type แต่ว่าภาษาที่ไม่ได้ออกแบบ union type มาก็จะไม่ได้ support เครื่องมือในส่วนนี้มากเช่น เราไม่ควรต้องกำหนด type ด้วยมือเอง หรือเวลาตรวจว่าค่าของ C เป็นชนิดอะไรก็ต้องเช็กเทียบด้วยมือ ถ้าเป็นภาษาที่ถูกสร้างมาโดยมีแนวคิด union type ตั้งแต่แรกจะสบายกว่านี้ครับ