Category Theory (for Programmer!) 2: Functor~คืออะไร
Tags: beginners, functional, programming, categorytheory
Published: 2020-01-12 14:35:04

# Functor คืออะไร?

> เนื้อหาเกี่ยวกับคณิตศาสตร์ในบทความนี้ได้รับคำปรึกษาจาก [@muitsfriday](https://medium.com/@muitsfriday)

## บนนำ

ก่อนจะเข้าเรื่อง **Functor** เรามาพูดเรื่องนี้กันก่อน ... เรื่องของกล่องและการแพ็กของ!

![](https://image.freepik.com/free-photo/close-up-hand-packing-parcel-boxes-prepare-delivery_1382-154.jpg)

จากในบทที่แล้ว เราได้อธิบายเรื่อง object และ arrow (หรือ morphism) ในเรื่อง category กันไปแล้วด้วยเรื่องของ**มันฝรั่ง**

![](https://1.bp.blogspot.com/-fN3Lweshq4E/Xgi-08onREI/AAAAAAAAR_8/WD0Hh1XiVIsfrjC3jGOdk4fOw9YSWk3UgCNcBGAsYHQ/s320/category-1.png)

เช่น ถ้าเรามี**มันฝรั่ง** (object) อยู่ เราสามารถเอาไป**ทอด** (arrow) แล้วเราก็จะได้**มันฝรั่งทอด** (object) ออกมา

![](https://1.bp.blogspot.com/-Z2xmwWKVUKU/XhrcF07_O0I/AAAAAAAASEY/IHbaaHOuwD0xcX3e9MQiCvPpeh-Mae82gCNcBGAsYHQ/s1600/cg-1.jpg)

ซึ่งถ้ามีแค่นี้มันก็ยังไม่มีอะไร ดังนั้นในบทนี้เราจะมาพูดถึงตัวละครใหม่อีกหนึ่งตัวนั่นคือ...

## Just wrap it! ..ด้วย"กล่อง"

ขอเทียบกับโลกแห่งมันฝรั่งเหมือนเดิมละกัน

ถ้าเราไปซื้อมันฝรั่งที่ร้านค้า ร้านมันจะเอามันฝรั่งเราใส่ถุงกลับบ้านให้ ... แต่ในยุคนี้ที่เรากำลังลดการใช้ถุงพลาสติกกัน ร้านค้าเลยบอกว่าเดี๋ยวจะเอามันฝรั่งใส่กล่องกลับบ้านให้แทนละกัน

ได้แบบนี้

![](https://1.bp.blogspot.com/-Fl2a4JtHr_o/Xhn7xVY1LLI/AAAAAAAASDo/Yry0p96r7xIdalpKJNwhjF3JCxP_QVFywCNcBGAsYHQ/s1600/cg-5.jpg)

คือจาก **มันฝรั่ง** ก็กลายเป็น **มันฝรั่งในกล่อง** แทน

```
มันฝรั่ง --> กล่อง(มันฝรั่ง)
value --> box(value)
```
ซึ่งการเอาของใส่กล่องเนี่ย เราเรียกว่า

> **Lift** หรือ **Unit** ความสามารถคือการห่อของอย่างเดียว ทำอย่างอื่นไม่ได้เลย

![](https://1.bp.blogspot.com/-0DVfzCd-3ek/Xhs_BwpokcI/AAAAAAAASFI/F8TXyu4yFr0FFD6KEz7cRoiR2XBjG4TAwCNcBGAsYHQ/s400/cg-4-1.jpg)

แต่การที่เราเอามันฝรั่งใส่กล่องแบบนี้ ก็ทำให้เกิดปัญหาตามมา คือ

![](https://1.bp.blogspot.com/-OmdrPaJWDT0/XhrcFk4lxVI/AAAAAAAASEQ/BljPugzZVrkQJVvHZuwTzyQHDXpXPjJwgCNcBGAsYHQ/s1600/cg-2.jpg)

สถานการณ์อย่างนี้, การ**ทอด**เดิมของเรา ไม่สามารถเอามาใช้งานได้แล้ว เพราะเราไม่สามารถทอดกล่องได้นั่นเอง

ดังนั้นเราเลยต้องการ**ผู้ช่วย**เพิ่มอีกหนึ่งคนที่จะเอาของในกล่องของเราไป**ทอด**ให้เราได้ (และให้ผลลัพธ์ออกมาเป็น**มันฝรั่งทอด**ในกล่องเหมือนเดิมด้วย!) ซึ่งเราจะเรียกผู้ช่วยคนที่ 2 นี้ว่า

> **Map** หรือ **Fmap** ความสามารถคือการรับงาน (job) อะไรบางอย่างเข้าไป แล้วเอาทำกับของที่อยู่ข้างในกล่อง

![](https://1.bp.blogspot.com/-mOriIYWs_sY/Xhs_CFx_72I/AAAAAAAASFM/vDKXoHymPNkRKJ2jVgr-7jNMsdtqu6GVgCNcBGAsYHQ/s400/cg-4-2.jpg)

สรุปอีกครั้งคือถ้าเราต้องการ **ทอดมันฝรั่ง(ที่อยู่ในกล่อง)** เราต้องการผู้ช่วย 2 คน

1. **Lift**: เป็นคนเริ่มนำมันฝรั่งเข้าไปใส่ในกล่อง
2. **Map**: รับงานเข้าไปทำกับมันฝรั่งในกล่อง

เท่านี้เราก็สามารถที่จะห่อมันฝรั่งและก็ทอดมันฝรั่งในกล่องได้แล้ว

![](https://1.bp.blogspot.com/-iC4VrWGTgwM/XhtCS7OpTOI/AAAAAAAASFs/ATorV5COJJ8ITCIjQzxHrCdkHik-_4jUgCNcBGAsYHQ/s1600/cg-3.jpg)

แล้วถ้าเราเอาผู้ช่วย 2 คนนี้มารวมร่างกัน เราจะได้ผู้ช่วยคนใหม่ออกมา 1 คนที่มีความสามารถทั้ง Lift + Map

ในโลก category เราจะเรียก**ผู้ช่วย**ที่ทำหน้าที่แบบนี้ได้ว่า ***"Functor"*** นั่นเอง!!

![](https://1.bp.blogspot.com/-lTkEaG0Sgz8/Xhs_Cv1DucI/AAAAAAAASFQ/DNDA5aXSheQdQVLgwFmcw-QUlkbb-jwkwCNcBGAsYHQ/s1600/cg-4-3.jpg)


---

# Functor ในมุมมองโปรแกรมเมอร์

มาพูดเรื่องของฟังก์เตอร์ในมุมของภาษาคอมพิวเตอร์กันบ้าง

ถ้าเรามี *value* อยู่ เราสามารถห่อมันได้ด้วยการสร้างฟังก์ชันที่ชื่อว่า `Just` ขึ้นมา

![](https://1.bp.blogspot.com/-a4ZMzaENwpY/Xhn7w0G-MvI/AAAAAAAASDg/2g7iAH52mSoHElIrcDq73XZKfTQdFj8LgCNcBGAsYHQ/s1600/cg-6.jpg)

## Just

กล่องที่เราพูดถึงกันเมื่อกี้ก็คือ Container ชนิดหนึ่ง ที่เราสามารถเอามา wrap ค่าของเราได้ ซึ่งเราจะเรียกมันว่า **Just**

แปลง่ายๆ ก็ประมาณ *"ก็แค่ห่อมันเอาไว้~"*

```js
const Just(v) => {
    value: v
}

let one = 1
let oneInTheBox = Just(x)

print(oneInTheBox.value) // 1
```

โอเค ไม่ยาก สร้างฟังก์ชันที่รับค่าเข้าไป 1 ค่าแล้วรีเทิร์นกลับมาเป็น object ที่ห่อหุ้มค่านั้นเอาไว้!

หรือถ้าเราใช้ภาษาโปรแกรมแบบ static type อาจจะเขียนแบบนี้ก็ได้

```java
class Just<T> {

    T value;

    Just(T v) {
        this.value = v;
    }
}

Int one = 1;
Just<Int> oneInTheBox = new Just<Int>(one);

print(oneInTheBox.value); // 1
```

แต่!

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

```js
function plusTwo(x) {
    return x + 2
}

let one = 1
print( plusTwo(one) ) // 3

let oneInTheBox = Just(x)
print( plusTwo(oneInTheBox) ) // Error! ไม่สามารถนำ Just มาบวกเลขได้!
```

![](https://1.bp.blogspot.com/-UCqiC8mmZ5g/Xhn7xfj01yI/AAAAAAAASDk/e32Mife_e38hImQsptzP3m2Yt8zD1iNnwCNcBGAsYHQ/s1600/cg-7.jpg)

ทางแก้ก็คือ

เมื่อกี้เราเราบอกว่านอกจากการ "ห่อ/แกะห่อ" แล้วเนี่ย มันยังต้องมีอีกความสามารถหนึ่งนั่นคือการรับ job เข้าไปทำงานกับ item นั้น ซึ่งเราเรียกความสามารถนี้ว่า

> **"map"** หรือใน **"fmap"**

ก็จัดการเพิ่ม method `map` ลงไปใน `Just` ที่เราเขียนไว้เมื่อกี้

```js
const Just(v) => {
    value: v,
    map(f) {
        return Just( f(v) )
    }
}
```
สร้างฟังก์ชัน `map` ซึ่งสามารถ
1. รับฟังก์ชันหรือ *job* เข้าไป
2. เอาฟังก์ชันนั้นไป apply กับ value ที่เก็บไว้
3. ผลลัพธ์ที่ได้ก็เอาไปใส่กล่อง (ห่อด้วย Just) ใหม่อีกรอบ

> ### Note
> สำหรับภาษาสไตล์ Functional เช่น Haskell เราสามารถสร้าง Functor ได้ด้วยโค้ดหน้าตาประมาณนี้ (ตัวอย่าง ต้องการจะสร้าง Functor `f`)
> ```hs
> class Functor f where
>   fmap :: (a -> b) -> f a -> f b
> --           │         │      └─output: Functor f ที่หุ้ม b อยู่
> --           │         └─ input: Functor f ที่หุ้ม a อยู่
> --           └─ input: รับ function ที่รับค่า a เข้าไปแล้วคำนวณค่า b กลับมาให้
> ```

ต่อไป เรามาดูตัวอย่างการใช้งาน

```js
function plusTwo(x) {
    return x + 2
}

let oneInTheBox = Just(1)
let threeInTheBox = one.map(plusTwo)
```

ดังนั้น, การเอา `กล่อง(1)` ไป `map` ด้วยฟังก์ชัน `plusTwo` ผลที่ได้ก็คือ 3 ที่อยู่ใน `กล่อง(3)` ละ

---

## Nothing

เรามี `value` ไปแล้ว

และเราก็มีกล่องสำหรับห่อค่าเอาไว้คือ `Just` แล้ว

คำถามคือ .. เป็นไปได้มั้ย ที่มันจะมีแค่**กล่องเปล่าๆ**อย่างเดียว !?

คำตอบ .. เกริ่นมาซะขนาดนี้ แน่นอนว่ามันต้องมีสิ! และเราก็เรียกกล่องเปล่าพวกนั้นว่า `Nothing` นั่นคือการไม่มีค่าอะไรเก็บอยู่เลย

![](https://1.bp.blogspot.com/-XIBRVz4X5-o/XhrqIAd5AnI/AAAAAAAASE8/BSpigOvvjWs9o9tC9R8AAxfQ9PaJh9EEwCNcBGAsYHQ/s1600/cg-9.jpg)

แต่ Nothing หรือกล่องเปล่าเนี่ย มันก็ทำให้เกิดปัญหาได้ พอมันไม่มี `value` ตอนเราสั่ง `map` มันก็จะไม่มีค่าให้เอาไป apply กับฟังก์ชันยังไงล่ะ

```js
const Nothing() => {
    value: null,
    map(f) {
        return Just(f(null))
    }
}
```

การที่ไม่มีค่า ทำให้ตอน apply `f()` อาจจะเกิดปัญหาขึ้นได้

วิธีแก้คือหาก functor ของเราเป็นแบบ `Nothing` การ apply function อะไรก็ตามจะถูก ignore (ทำเป็นไม่สนใจ) ทั้งหมดเลย

![](https://1.bp.blogspot.com/-HEV-YxFRHs4/Xhn7xz_P_jI/AAAAAAAASDs/YRUoLbMd5RYJ6H1Do7Tx0THlq-0J0Y03wCNcBGAsYHQ/s1600/cg-8.jpg)

และหากเราเอาทั้ง `Just` และ `Nothing` มารวมกัน ก็จะได้กล่องชนิดใหม่ขึ้นมาอีก นั่นคือ

> **"Maybe"**

ซึ่งมีโครงสร้างประมาณนี้

```js
const Maybe = (v) => {
    value: v,
    map(f) {
        if(v != null) {
            return Maybe(f(v))
        } else {
            return Maybe(null)
        }
    }
}

// หรือจะย่อให้สั้นลงอีกหน่อยก็ได้

const Maybe = (v) => {
    value: v,
    map(f) {
        return Maybe(v != null ? f(v) : null)
    }
}
```
นั่นคือ `Maybe` จะเพิ่มการเช็กว่าถ้า `value` ไม่มี ก็จะไม่รันฟังก์ชัน แต่ตอบเป็น `Maybe(null)` = `Nothing` นั่นเอง

ต่อมา เราสามารถเพิ่ม method สำหรับเอาไว้เช็กว่า `Maybe` ตัวนี้เป็น `Just(x)` หรือเป็น `Nothing` กันแน่

```js
const Maybe = (v) => {
    value: v,
    map(f) {
        return Maybe(v != null ? f(v) : null)
    },
    isNothing() {
        return v == null
    }
}
```

# แล้วเอาไปใช้อะไรได้?

ต่อไปลองมาดูตัวอย่างการทำ **Functor** ไปใช้งานบ้าง

สมมุติว่าเรามีฟังก์ชันอยู่ตัวนึง ให้เป็นฟังก์ชันที่ทำการคำนวณค่าอะไรสักอย่าง

```js
function calculateSomething(x) {
    return 50 / (x - 100) + 20
}
```

ซึ่งถ้าเราลองมาวิเคราะห์ดู ถ้าค่า `x` ที่ใส่เข้าไปมีค่าเป็น `100` จะทำให้เกิดการ **Dividing by Zero** หรือการหารด้วย 0 นั่นเอง

วิธีการแก้แบบง่าย เราก็จะเติม `if` เพื่อเช็กเข้าไปก่อนที่จะทำการคำนวณ แบบนี้

```js
function calculateSomething(x) {
    if(x == 100) return null
    return 50 / (x - 100) + 20
}
```

ฟังก์ชันแบบนี้เราเรียกว่า **"MayError"** นั่นคือเป็นฟังก์ชันที่ไม่ได้ให้คำตอบออกมาเสมอไป แต่อาจจะเกิดการ Error ได้ (ซึ่งในเคสนี้ เราตั้งไว้ว่าถ้ามีอะไรผิดพลาด ให้ตอบกลับมาเป็น `null` แทน)

ตัวอย่างต่อมา เรากำหนดให้มีฟังก์ชันแบบ MayError แบบนี้สัก 3 ตัวแบบนี้ .. ก็คือเป็นฟังก์ชันที่สามารถคำนวณค่าอะไรบางอย่างออกมาได้ แต่ก็มีโอกาสบางเคสที่สามารถเกิด Error ได้เช่นกัน

```js
function calculateThis(x) {
    return ...
}

function calculateThat(x) {
    return ...
}

function calculateThose(x) {
    return ...
}
```

หากเราต้องคำนวณค่าคำตอบจากฟังก์ชันทั้ง 3 ตัวแบบเรียงตามลำดับ `calculateThis` --> `calculateThat` --> `calculateThose`

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

```js
let a = 100
let b = calculateThis(a)
if(b) {
    let c = calculateThat(b)
    if(c) {
        let ans = calculateThose(c)
        if(ans) {
            print('answer is ' + ans)
        } 
    }
}
```
หรืออาจจะจัดรูปใหม่ให้ไม่เป็นโค้ดแบบ [Pyramid of doom](https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)) ได้แบบนี้
```js
let a = 100
let b = calculateThis(a)
let c = b == null ? null : calculateThat(b)
let ans = c == null ? null : calculateThose(c)

if(ans) {
    print('answer is ' + ans)
} else {
    print('no answer')
}
```

แต่ถ้าเราเอา Functor ในรูปของ `Maybe` มาใช้งาน เราก็จะได้โค้ดแบบนี้

```js
let a = Maybe(100)
let b = a.map(calculateThis)
let c = b.map(calculateThat)
let ans = c.map(calculateThose)

// หรือเขียนแบบต่อกันเป็น chaining

let ans = Maybe(100)
    .map(calculateThis)
    .map(calculateThat)
    .map(calculateThose)

if( ans.isNothing() ) {
    print('no answer')
} else {
    print('answer is ' + ans.value)
}
```

เราสามารถสั่งให้ object `Maybe` ทำงานตามฟังก์ชันแต่ละสเต็ปแบบไม่ต้องกลัวว่ามันจะเกิดการ Error หรือไม่ เพราะตัว Functor `Maybe` นั้นมีการเช็กว่าค่าตอนนี้เป็น `Nothing` อยู่หรือไม่ ถ้าไม่ก็ไม่ทำงาน

# สรุป

Functor แบบสรุปง่ายๆ ก็คือ Abstract Model ของ Container ที่เอาไว้ห่อ `value` และก็ต้องมี map สำหรับให้ใส่ฟังก์ชันเข้าไปทำงานกับค่าข้างในได้