ทฤษฎีแคตากอรีเป็นหนึ่งในสาขาของวิชา Mathematics ซึ่งเป็นแนวคิดที่ต่อมาถูกเอามาใช้เป็นหลักการออกแบบภาษาโปรแกรมคอมพิวเตอร์ ดังนั้นจะบอกว่าเป็นหัวข้อทางคณิตศาสตร์ที่ programmer ที่ไม่ได้มาจากสาย math สามารถเรียนรู้และทำความเข้าใจได้ง่ายเพราะคุ้นเคยกับมันอยู่นิดหน่อยแล้วก็ว่าได้
Concept
Category เป็นการประกอบกันของ 2 สิ่ง นั่นคือ
- Object
- Arrows (หรือ Morphism อ่านว่า "มอร์ฟิซึม")
Object นั้นจะเป็นอะไรก็ได้ แต่ละ Object สามารถแปลงไปเป็นอีก Object หนึ่งได้ด้วยการเขียนลูกศร Arrow ลงไป
อย่างนี้ตัวอย่างข้างบนนี้ เรามี Object ชื่อ A ซึ่งแปลงไปเป็น B ได้ด้วย Arrow f
(หรือ Morphism f
) จากนั้นเราก็สามารถแปลง B ให้กลายเป็น C ได้ด้วย Arrow g
อีกทีหนึ่ง
จากนิยามแค่นี้ น่าจะไม่เข้าใจและไม่เห็นภาพแน่นอน งั้นมาดูตัวอย่างกัน
สมมุติว่า Category ของเราเป็นโลกแห่ง "มันฝรั่ง" เราอาจจะสร้าง Category ได้ประมาณนี้
- Object: มันฝรั่งแบบเป็นลูก, มันฝรั่งหั่น, เฟรนฟราย, มันฝรั่งแผ่นทอด
- Arrow: การหั่น, การทอด
ป.ล. จริงๆ มันต้องหั่นเป็นแผ่น หรือ หั่นเป็นแท่ง แล้วค่อย --> ทอดนะ ...แต่หารูปประกอบไม่ได้!! (ฮา)
แล้วเรื่องนี้มันเกี่ยวอะไรกับการเขียนโปรแกรมด้วย คงสงสัยกันอยู่ละสิ! นั่นก็คือ...
Category of Type
สำหรับโลก Programming, ถ้าในมุมมอง Category แล้วเราจะมองมันเป็น Category of Type คือเป็นกลุ่มของตัวแปรชนิดต่างๆ มารวมกัน ไม่ว่าจะเป็น int
, double
, string
, boolean
อะไรพวกนั้น
ส่วน Arrow ก็คือ function
ในโปรแกรมมิ่งนั่นเอง เพราะฟังก์ชันจะรับตัวแปร (เทียบกับ object) เข้าไป แล้วคายตัวแปรอีกแบบกลับมา เช่น สร้างฟังก์ชันที่รับ int
และ return string
กลับมาอะไรแบบนั้น
Compose
คอมโพสแปลได้ว่า "การประกอบเข้าด้วยกัน" ในเชิงของ Category ถ้าเรามีความสัมพันธ์ A --> B --> C
แล้วเราสามารถสร้างทางลัดตรงจาก A --> C
ได้เลย เรียกว่าการ "Compose" นั่นเอง
แล้วถ้าเรามองว่า f
เป็นฟังก์ชันสำหรับแปลง A --> B และ g
เป็นฟังก์ชันแปลงจาก B --> C ถ้าเขียนเป็นโค้ดก็จะได้แบบนี้
B = f(A)
C = g(B)
ถ้าเราจะทำการ compose สองฟังก์ชันนี้เข้าด้วยกันก็จะเขียนได้ว่า
C = g(f(A))
หรือเขียนแบบคณิตศาสตร์จะได้
C = (g ∘ f)(A) //อ่านว่า "g of f" หรือ "g after f"
note: การ compose ฟังก์ชันในคณิคศาสตร์จะคล้ายๆ กับการเขียน "Pipeline" ในโลกโปรแกรมมิ่ง
A |> f
|> g
หรือ
A |> a => f(a)
|> b => g(b)
ข้อแตกต่างที่ควรระวังคือ (g ∘ f) หมายถึงทำงาน
f
->g
นั่นคือทำงานจากฟังก์ชันข้างหลังก่อนคือเรียง ขวาไปซ้าย แต่ถ้าเขียนแบบ pipeline การทำงานจะเป็นแบบอ่านตามลำดับคือ ซ้ายไปขวา/บนลงล่างnote: ฟีเจอร์การเขียน pipeline ไม่ได้มีในทุกภาษา ตรวจสอบก่อนใช้งานด้วย
ถ้ากลับไปที่โลกมันฝรั่งของเรา ก็อาจจะสร้าง Arrow เพิ่มได้แบบนี้
แล้ว Category มีกฎอะไรอีกบ้างนะ?
อะไรจะถือว่าเป็น Category บ้างมีกฎง่ายๆ (แต่ก็ไม่ง่าย! เอ๊ะ ยังไง?) อยู่ 2 ข้อคือ...
- Composition Associative
- Identity Arrow
มาดูข้อแรกกันก่อน
Composition Associative: คุณสมบัติการเปลี่ยนกลุ่ม
ก่อนอื่นก็ต้องมีคุณสมบัติการสลับที่ เรื่องนี้เป็นเรื่องเดียวกับการสลับที่ในวิชาเลขนั่นแหละ แบบนี้
(1 x 2) x 3 = 1 x (2 x 3)
นั่นคือถ้าเรามี Category A, B, C, D อยู่ตามภาพนี้
เราเริ่มต้นด้วยการมี Arrow 3 ตัวคือ f
, g
, และ h
เราสามารถเอาทั้งหมดมาต่อ/ผสมกันได้แบบนี้
h ∘ g ∘ f
= h ∘ (g ∘ f)
= (h ∘ g) ∘ f
นั่นคือไม่ว่าเราจะทำฟังก์ชันไหนก่อน ก็จะได้ผลลัพธ์ออกมาเหมือนกัน (แต่สลับลำดับไม่ได้นะ เช่นจะเรียงแบบ g ∘ h ∘ f
นี้ไม่ได้)
Identity Arrow: คุณสมบัติการมีเอกลักษณ์
ทุกๆ Object จะต้องมี Arrow ที่วิ่งกลับเข้าหาตัวเองเสมอ แบบนี้
หากเราเอา Identity Arrow ตัวนี้ไปทำการ compose กับ Arrow ตัวอื่น ผลลัพธ์จะได้ Arrow ตัวนั้นเสมอ (เสมือนว่า Arrow ตัวที่เป็น Identity นั้นจะหายไปเลย)
แบบนี้
{id} ∘ f = f
f ∘ {id} = f
หรือถ้าเขียนเป็นโปรแกรมมิ่งก็จะได้ว่า
function identity(x){
return x;
}
หรือถ้าใช้ภาษาโปรแกรมสไตล์ FP ก็จะได้แบบนี้
identity :: A => A
identity x = x
ต่อไปมาดูตัวอย่างกันหน่อย
สมมุติเรามี array อยู่หนึ่งตัว ถ้าเราจะสร้าง identity ขึ้นมาก็จะได้แบบนี้
[1, 2, 3, 4] ++ [] = [1, 2, 3, 4]
[] ++ [1, 2, 3, 4] = [1, 2, 3, 4]
concat
หรือการ "ต่อ array" ก็จะมี "Empty Array" (อะเรย์ว่าง) เป็นเอกลักษณ์ของ array เพราะไม่ว่าเราจะเอา empty array ไปต่อกับ array ตัวไหนก็ตามจะได้ array ตัวเดิมเสมอ
note: สัญลักษณ์ concat ที่ใช้ในตัวอย่างใช้ ++
ซึ่งแต่ละภาษาโปรแกรมก็อาจจะมีวิธีเขียนที่ต่างกัน เช่นบางภาษาอาจจะใช้ +
, :
, .concat()
, .extend()
, หรือ .merge()
แทน
ถ้า array ยากไปลองมาดูตัวที่ง่ายขึ้นอีกนิด กับ Int
x + 0 = x
0 + x = x
+0
เป็นเอกลักษณ์ของตัวเลข ไม่ว่าจะเอาไปบวกกับอะไรก็ได้เลขตัวเดิมเสมอ
หรืออาจจะใช้การคูณแบบนี้
x * 1 = x
1 * x = x
แบบนี้ก็ได้นะ (identity ไม่จำเป็นต้องมีแค่ตัวเดียว)
เพราะ *1
ก็เป็นเอกลักษณ์ของตัวเลขเช่นกัน
แต่ก็ต้องอธิบายเพิ่มเติมว่าการบอกแบบนี้ได้ จะต้องกำหนดว่า category ของเราเป็น Category of Integer เท่านั้นนะ!
เพราะถ้าเรากำหนด category ของเราเป็น Category of Type เช่นชนิดตัวแปรในภาษาโปรแกรม เราจะได้ว่าไม่ว่าจะเอาตัวเลขตัวไหนมาบวกหรือคูณกันก็ถือเป็นเอกลักษณ์ทั้งนั้น (ไม่เข้าใจเหรอ งั้นมาดูตัวอย่างต่อ)
1 + 2 = 3
7 - 6 = 1
4 * 5 = 20
แบบนี้ถือว่าเป็น identity ได้ทั้งหมด เพราะไม่ว่าเราจะเอาไปทำ operation ตัวไหนก็ได้ผลออกมาเป็นค่าเดิม! เพราะว่า...
Int + Int = Int
Int - Int = Int
Int * Int = Int
เพราะถ้าเราโฟกัสที่ Type ไม่ว่าจะเป็น 1, 2, 3 ก็จัดเป็น Int
ทั้งนั้น
และเช่นกันนะ ถ้าในมุมมองของ Category of Type การ concat array เมื่อกี้นี้ก็ไม่จำเป็นต้องเป็น Empty Array เท่านั้นนะ จะเอาarrayตัวไหนมาconcatกันก็ถือว่าเป็น identity ทั้งนั้น เพราะ
Array ++ Array = Array
สรุป
ในบทความนี้เป็นการแนะนำเนื้อหา Category Theory แบบคร่าวๆ มากๆ ซึ่งพื้นฐานคอนเซ็ปในการเอาไปศึกษาต่อในเรื่องของ Functional Programming
แต่ถ้าอ่านมาจนถึงตอนนี้อาจจะยังไม่เห็นภาพชัดเจน (ก็คือยังไม่รู้นั่นแหละว่าเราจะเอาไป apply กับการเขียนโปรแกรมได้ยังไง) เพราะว่ายังมีอีกหลายเรื่องที่ต่อจากนี้ ไม่ว่าจะเป็น functor
, ord
, monad
ซึ่งเรื่องพวกนี้ถูกเอาไปใช้เป็นคอนเซ็ปของการออกแบบภาษาโปรแกรมสาย Functional Programming (FP) เลย ถ้าเรารู้พื้นฐานพวกนี้ดี การศึกษา FP ก็จะไปได้เร็วมากขึ้น
Top comments (0)