FP(04): โครสร้างแบบ Pair, Either และ List Comprehension การสร้างลิสต์ฉบับฟังก์ชันนอล
Tags: beginners, functional, programming
Published: 2020-05-15 17:12:01

> ติดตามบทความอื่นๆ ของเราได้ที่ [TAMEMO.com](https://www.tamemo.com)

---

> บทความนี้จะเน้นที่ **List Comprehension** เป็นหลัก เพราะแนวคิดของโครงสร้างแบบ Pair และ Either **muitsfriday** เขียแบบละเอียดไปเรียบร้อยแล้ว ในหัวข้อของ [Product (Pair)](https://www.muitsfriday.dev/posts/category-theory-1-product) และ [Co-Product (Either)](https://www.muitsfriday.dev/posts/category-theory-2-coproduct) ซึ่งแนะนำให้อ่านก่อนอย่างยิ่งเลยนะ จะเขียนใจวิธีการออกแบบภาษาแนว FP เลย
> 
> เนื้อหา Pair และ Either ในบทความนี้จะเกริ่นให้พอเข้าใจเพื่อปูเรื่องต่อไปยัง List เท่านั้น)

ในบทที่แล้วเราพูดถึงการจัดการข้อมูลในโครงสร้างแบบลิสต์กับไปแล้ว แต่เรายังไม่ได้อธิบายเลยว่าในโลก FP นั้นมีแนวคิดในการสร้างตัวแปรแบบลิสต์ขึ้นมายังไงกัน รวมถึงตัวแปรโครงสร้างข้อมูล (Data Structure) ที่ใช้งานใน FP ด้วย

## Pair: 2 ค่าอยู่ในตัวแปรตัวเดียว

ในบทที่แล้วเราพูดถึงมิติของตัวแปรที่เรียกว่า **Tensor** โดยตัวแปรที่เก็บค่าธรรมดาเรียกว่า Tensor Rank 0 หรือ Scala

แล้วเราก็ได้เรียนรู้ว่าถ้าต้องการจะเก็บตัวแปร 2 ค่าขึ้นไปจะต้องเปลี่ยนไปใช้ Tensor ที่ rank มากกว่าเดิม เช่น **List**

แต่ก่อนที่จะไปเรื่องต่อไป เราขอพูดถึงโครงสร้างที่เป็นต้นกำเนิดของลิสต์กันก่อน **ซึ่งถือว่าเป็นชนิดตัวแปรแบบพื้นฐานสุดๆ ใน FP เลย** นั่นคือตัวแปรที่เรียกว่า **"Pair" แพร์** หรือก็คือตัวแปรคู่ เก็บได้ 2 ค่า!

Pair คือตัวแปรที่เก็บค่าได้ 2 ค่า (หมายเหตุ แต่ละภาษาจะมี syntax วิธีการเขียนต่างกันไปนะ)
```js
var x = (123, "ABC")
```
ถ้าถามว่า แค่นี้เหรอ?

ใช่แล้วครับ แค่นี้แหละ คอนเซ็ปของ Pair คือตัวแปรที่เก็บค่าได้เพิ่มอีก 1 ค่า (กลายเป็น 2 ค่า เท่านั้นแหละ!!)

### แล้วสร้าง Pair ยังไงล่ะ?
ถ้าคุณเป็นสาย OOP การจะสร้างตัวแปรที่เก็บค่าคู่ได้ ก็ไม่มีอะไรยาก คือเราสามารถสร้างคลาสที่เก็บตัวแปร 2 ตัวเอาไว้ (สมมุติว่าชื่อ `first` และ `second`) 

```java
class Pair<F,S>{
  F first
  S second
  Pair(first, second){
    this.first = first
    this.second = second
  }
}

var entry = Pair(123, "ABC")
print(entry.first)  //123
print(entry.second) //ABC
```
แต่ไหนๆ แล้วเราก็กำลังศึกษาการเขียนโปรแกรมแบบ FP อยู่ ไหนลองมาดูวิธีสไตล์ FP หน่อย
```js
function pair(first, second){
  return [first, second]
}

function first(pair){
  return pair[0]
}

function second(pair){
  return pair[1]
}

var entry = pair(123, "ABC")
print(first(entry))  //123
print(second(entry)) //ABC
```
จะสังเกตว่า FP นั้นเวลาเราออกแบบ เราจะเริ่มจากการคิดว่าสร้างฟังก์ชันขึ้นมาเพื่อ evaluate ข้อมูลอะไรบางอย่างออกมาเป็นอีกค่าหนึ่ง 

เช่นในกรณีนี้ เราสร้างฟังก์ชัน `pair()` สำหรับสร้างแพร์ขึ้นมา จากนั้นถ้าเราต้องการดึงค่าออกมา ก็เหมือนเดิมคือต้องทำผ่านฟังก์ชันนั่นแหละ ก็สร้างขึ้นมาอีก 2 ตัว คือ `first()` และ `second()` ที่เมื่อโยนแพร์ลงไป จะได้ค่าตัวแรกหรือตัวที่สองออกมา

จุดสังเกตอีกจุดคือ เราสร้าง `pair()` โดยซ่อนโครงสร้างจริงๆ ไว้ข้างใน เช่นในตัวอย่างนี้เราสร้างมันด้วย Array ... แต่ความจรืง เราจะสร้างมันด้วยอะไรก็ได้นะ ตราบใดที่ `first()`, `second()` สามารถดึงค่าออกมาให้เราได้ถูกต้อง

### การเอา Pair ไปใช้งาน
ส่วนใหญ่เราจะใช้งานแพร์เมื่อต้องการเก็บค่ามากกว่า 1 ตัว ตัวอย่างที่เห็นชัดที่สุดก็เช่นการรีเทิร์นค่าจากฟังก์ชัน (เพราะฟังก์ชันรีเทิร์นค่ากลับได้แค่ 1 ค่า ถ้าอยากจะรีเทิร์นกลับมากกว่านั้นก็ต้องใช้ Pair นี่ล่ะ)
```js
function getWickedData(){
  var status = ...
  var data = ...
  return pair(status, data)
}

var entry = getWickedData()
var status = first(entry)
var data = second(entry)
```
แต่ส่วนใหญ่ภาษาที่มีโครงสร้างแบบ Pair มาให้ จะมีฟีเจอร์ที่เรียกว่า destruct มาให้ด้วย นั่นคือการแตก pair ออกเป็นค่า 2 ค่าให้เลย แบบนี้
```js
var (status, data) = getWickedData()
```




![](https://4.bp.blogspot.com/-OTXLeFfP39E/XrK0e3_UWVI/AAAAAAAASTc/Om8h4vUd8XYbnlZPnjwkGMxliSL4UuyjQCNcBGAsYHQ/s1600/fp-04-2.png)

ถ้าเรามีโครงสร้างแบบ Pair ซึ่งสามารถกลายเป็นค่าได้ 2 ค่าต่อไป มันก็จะมีโครงสร้างอีกแบบหนึ่งที่**ตรงข้าม**กับ Pair เลยเพราะนี่เป็นโครงสร้างที่อนุญาตให้คุณเก็บค่าได้สองชนิดเลยนะ แค่เก็บได้ทีละ 1 ค่าเท่านั้น

## Either: ตัวแปรตัวเดียว แต่เป็นได้ 2 ค่า
ในบางภาษา (เช่นตัวอย่างนี้ใช้ภาษา TypeScript) จะอนุญาตให้เราสร้าง type ที่เป็นไทป์ผสมระหว่าง 2 (หรือมากกว่านั้น) ได้ เช่น
```ts
type EitherNumberOrString = number | string
```
นั่นคือเราสามารถกำหนดค่าใส่ตัวแปรชนิดนี้ได้ทั้งตัวเลขและตัวอักษร แบบนี้
```ts
let data: EitherNumberOrString
data = 1
data = "A"
```
### การเอา Either ไปใช้งาน
ส่วนใหญ่ Either จะใช้กับกรณีค่าที่รีเทิร์นกลับนั้นมี 2 state(หรือมากกว่านั้น) เช่นฟังก์ชันโหลดข้อมูลของเรา อาจจะตอบกลับเป็น Error ก็ได้
```ts
type MayError = Data | Error

function getWickedData(): MayError{
  if(...) {
    return Data(...)
  } else {
    return Error(...)
  }
}

let res = getWickedData()
if(res instanceof Data){ ... }
else if(res instanceof Error){ ... }
```

---
เอาล่ะ จบโครงสร้างแบบพื้นฐานที่เราเจอได้ใน FP กันไปแล้ว ต่อไปจะเป็นการพูดถึงโครงสร้างแบบ **List** ในมุทมอง FP กันต่อ

## Pair as List

จริงๆ แล้วโครงสร้างข้อมูลใน FP นั้นจบแค่ Pair เท่านั้นแหละ ทีนี้ ถ้าเราต้องการเก็บข้อมูลมากกว่า 2 ตัวเราจะทำยังไง?

คำตอบคือ **จับ Pair ซ้อน Pair ๆๆๆ เข้าไปเรื่อยๆ ไงล่ะ**
```js
var pair = pair("A", pair("B", pair("C", "D")))
```
ซึ่งแนวคิดของเอา Pair มาต่อๆ กันเนี่ยแหละที่มันจะกลายไปเป็นโครงสร้างแบบ List ต่อไป

หรือสรุปง่ายๆ คือ List เกิดจากการนำโครงสร้างที่เป็นหน่อยย่อยที่สุดอย่าง Pair มาประกอบเข้าด้วยกันไงล่ะ!

![](https://2.bp.blogspot.com/-qGMHUamkjYs/XrK0e9k_f1I/AAAAAAAASTU/OA5nNJ8_EmwBDPIWfh52jq2h0Wg5cgpHwCNcBGAsYHQ/s1600/fp-04-1.png)

ทีนี้ถ้าเราจะหยิบข้อมูลแต่ละตัวออกมา เราจะต้องทำยังไง?
![](https://2.bp.blogspot.com/-VWoM6sYmVGo/XrK0e61S3zI/AAAAAAAASTY/cSAJcF-h5Igd_uJ5Amv_X-HzWc9u4UKGQCNcBGAsYHQ/s1600/fp-04-3.png)
```js
var A = first(pair)
var B = first(second(pair))
var C = first(second(second(pair)))
var D = second(second(second(pair)))
```
ก็ค่อยๆ เรียกทีละชั้นๆ เริ่มจากข้างในออกข้างนอกนะ

อาจจะมองว่าวิธีการเรียกแบบนี้ยากกว่าการกำหนด index ตรงๆ แบบที่เราเคยทำ เช่น `arr[2]` หรือ `arr[8]` อะไรแบบนั้น แต่นั่นไม่ใช่ปัญหาสำหรับ FP เพราะการโปรเซสลิสต์ส่วนใหญ่ใน FP จะไม่อ้างไอเทมแบบเจาะจงเป็นตัวๆ แบบนั้นอยู่แล้ว

![](https://3.bp.blogspot.com/-gxbjbROcvBY/XrLT_5Cw3XI/AAAAAAAASUM/VLDALVvYmO82lMDWdFEL8J48YNO-1-3KwCNcBGAsYHQ/s1600/fp-04-5.png)

นอกจาก map, filter, reduce ที่เราพูดถึงกันในบทที่แล้ว ยังมีฟังก์ชันที่เอาไว้จัดการลิสต์ในเชิงการ getElement หรือ sublist ให้ใช้อีกเยอะ เช่น

| fn | return type | Note |
|---|---|---|
| `first` | element | ค่าตัวแรกในลิสต์ เหมือน arr[0] |
| `last` | element | ค่าตัวสุดท้ายในลิสต์ เหมือน arr[n-1]] |
| `tail` | list | sublist ตั้งแต่ตัวที่ 1 ถึงตัวสุดท้าย (ไม่รวมแค่ตัวแรก) |
| `init` | list | sublist ตั้งแต่ตัวถึงตัวรองสุดท้าย (ไม่รวมแค่ตัวสุดท้าย) |
| `take(x)` | list | เลือกตั้งแต่ตัวแรก ไป x ตัว |
| `skip(x)` | list | ข้าม x ตัวแรกไปจนถึงตัวสุดท้าย |

เช่น ถ้าเราอยากหยิบค่าตำแหน่งที่ `2` ออกมา ก็เขียนได้ว่า
```js
var C = first(skip(2, list))
```
แต่เอาจริงๆ แล้ว ภาษาโปรแกรมที่พวกเราใช้ๆ กันอยู่จะแปลงรูปนี้ให้อยู่ในเชิง method มากกว่า ก็จะได้แบบข้างล่างแทนนะ (ส่วนตัวคิดว่าอ่านและเขียนง่ายกว่าแบบฟังก์ชันเยอะ)
```js
var C = list.skip(2).first
//or
var C = list.take(3).last
```

---

## List Comprehension

หลังจากเข้าใจโครงสร้างแบบลิสต์กันแล้ว ลองมาดูฟีเจอร์การสร้างลิสต์สไลต์ FP กันต่อเลย

ตามปกติแล้ว เราสามารถกำหนดลิสต์ที่มีสมาชิกข้างในตรงๆ ได้
```js
var list = [1, 2, 3, 4]
```
แต่ถ้าเราต้องการลิสต์ที่มีซัก 1-100 ล่ะ จะต้องนั่งไล่พิมพ์เลขทีละตัวเหรอ? ก็คงไม่ใช่เรื่องล่ะ

หรือจะเขียนแบบนี้
```js
var list = [1,2 .. 100]
```
แน่นอนว่าคนอ่านออกและพอจะ**เดา**ได้เองว่า `..` ที่เว้นไว้น่ะต้องเติมเลขไปเรื่อยๆ จนถึง 100 

แต่คอมพิวเตอร์มันจะไปรู้เรื่องได้ยังไงกัน?

**ได้ยังไงกัน...จริงเหรอ??**

นั่นเพราะเรากำลังคิดแบบ imperative อยู่ยังไงล่ะ! สำหรับ FP แล้วการทำคำความเข้าใจลิสต์ที่โปรแกรมเมอร์ต้องการสร้างน่ะ **มันเป็นไปได้นะ!**

> Comprehension แปลว่า "ความเข้าใจ/ความหยั่งรู้" ก็ตามนั้นเลย List Comprehension เลยแปลว่าการที่คอมพิวเตอร์จะเข้าใจลิสต์แบบที่มนุษย์กำหนดขึ้นมาได้ยังไงล่ะ

โดยขอแบ่งเป็น 2 ฟีเจอร์ นั่นคือ **Range** และ **List Comprehension with Loop**

## Range
คำสั่ง `range` เป็นคำสั่งที่เอาไว้ **"สร้างช่วงของค่า ตั้งแต่ค่าหนึ่ง ไปจนถึงอีกค่าหนึ่ง"** ส่วนใหญ่จะต้องเป็นค่าที่สามารถเป็น sequence หรือเรียงค่ากันได้ เช่น number หรือ character อะไรแบบนั้น

และสำหรับภาษาสาย FP แท้ๆ แบบภาษา **Haskell** (รู้จักมั้ย?) สามารถทำความเข้าใจลิสต์ได้แบบเทพมากๆ เช่น
```hs
[1, 2 .. 100]
```
ตัวภาษาสามารถเข้าใจว่า เราต้องการสร้างลิสต์ ที่นับเพิ่มทีละ 1 ค่า เริ่มตั้งแต่ 1-100 ก็จะได้ค่าออกมา 1, 2, 3, 4, 5, 6 ไปเรื่อยๆ เลยจนถึง 98, 99, 100
แต่ยังไม่หมดแค่นั้น เราสามารถสร้างเลขที่กระโดดกันได้ด้วย เช่นต้องการสร้างลิสต์ของตัวเลขทั้งหมดที่หารด้วย 3 ลงตัวตั้งแต่ 0 ถึง 100 ก็เขียนได้แบบนี้
```hs
[0, 3 .. 100]
```
haskell ก็จะคิดว่าเราต้องการเลขในช่วง 0-100 แต่เดี๋ยวก่อน! ตัวต่อจาก 0 นั้นเป็น 3 แฮะ แสดงว่าโปรแกรมเมอร์ไม่ได้ต้องการเลขเรียงต่อกันแบบ 0, 1, 2 แล้ว มันต้องกระโดดทีละ 3 สินะ โอเค งั้นหลังจาก 0, 3 แล้วตัวต่อไปก็ต้องเป็น 6, 9, 12, ไปเรื่อยๆ ล่ะ

ความสามารถอีกอย่างชอง `range` ใน haskell คือมันสามารถสร้างลิสต์แบบ"**ปลายเปิด**"ได้ เช่น `[2, 4 ..]` นั่นคือสร้างลิสต์ 2 4 6 8 .. ไปเรื่อยๆ แต่ไม่ได้บอกจุดสิ้นสุด (เดี๋ยวเรื่องนี้เราจะไปขยายความต่อในบทของ Lazy Evaluation นะ)

จะเห็นว่ามันเป็นการสร้างลิสต์ที่เจ๋งมาก แทนที่โปรแกรมเมอร์จะมาพยายามทำความเข้าใจว่าเราจะกำหนดค่ายังไง (อาจจะต้องคิด logic หรือวนลูป) การเขียนแบบ FP จะเป็นการเอาใจฝั่มนุษย์ แล้วให้คอมพิวเตอร์เข้าใจเราแทน

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

เช่นถ้าเราต้องการลิสต์ตามเงื่อนไขแบบนี้

| Language | 1,2,3 | 1,2 (ไม่รวม 3) | 1,3,5,7 (กระโดดทีละ2) |
|---|---|---|---|
| Haskell | `[1..3]` | `init [1..3]` | `[1,3..7]`
| Python | - | `range(1,3)` | `range(1,7,2)`
| PHP | `range(1,3)` | - | `range(1,3,2)` |
| Kotlin | `1..3` | `1 until 3` | `1..7 step 2`
| Swift | `1...3` | `1..<3` | - |
| Ruby | `1..3` | `1...3` | `(1..7).step(2)` |

สำหรับ haskell นั้นถ้าไม่ต้องการตัวสุดท้าย ก็ใช้ฟังก์ชัน `init` ที่สอนไปแล้วมาตัดเฉพาะตัวหน้า

ส่วนตัวชอบแบบภาษา Swift ที่สุด ความหมายสื่อชัดดี ส่วนตัวที่ใช้แล้วสับสนทุกครั้งคือ Kotlin เพราะคำที่ใช้คือ `until` เช่น `1 until 3` มันแปลได้ว่า `1 ถึง 3` แต่ดันไม่รวมเลข 3 เข้าไปด้วย (แต่ Kotlin ก็ยังเป็นภาษาอันดับ 1 ในใจอยู่นะ ฮา)

หากใครเขียนได้หลายภาษาเชื่อว่าจะต้องมีความมึนงงเวลาใช้แน่นอน เพราะแต่ละภาษานั้นเขียนไม่เหมือนกัน หรือถึงเขียนเหมือนกันแต่ค่าที่ได้อาจจะไม่เท่ากันก็ได้ เช่น `range` ใน Python และ PHP ใช้ตัวเดียวกัน แต่ผลออกมาไม่เหมือนกัน

> **Note**: `range` ของภาษา Haskell เป็น Lazy Evaluation โดยตัวภาษาที่เป็น FP อยู่แล้ว, แต่ Python3 `range` จะเป็น Lazy เทียบได้กับ `xrange` ใน Python2 (Lazy Evaluation คืออะไร เดี๋ยวเราจะพูดกันต่อในบทหลังๆ นะ)


## List Comprehension with Loop

หลังจากเราเข้าใจวิธีการสร้างลิสต์ด้วยการใช้ `range` ไปแล้ว อาจจะมีข้อสงสัยว่า แล้วถ้าลิสต์ที่เราต้องการ มันเป็นตัวเลขที่ไม่ได้เรียงกันด้วย logic ง่ายๆ ล่ะ?

เช่นต้องการลิสต์ของตัวเลขตั้งแต่ 0-100 เฉพาะตัวที่หารด้วย 3 หรือ หารด้วย 5 ลงตัว เราจะสร้างกันยังไง?

แน่นอนว่า `range` นั้นรับมือกับเคสนี้ไม่อยู่แล้ว ขั้นแรกลองมาคิดแบบ imperative ที่เราคุ้นเคยกันก่อนดีกว่า

```js
for(i=0; i<=100; i++){
  if(i % 3 == 0 || i % 5 == 0){
    print(i)
  }
}
```
สำหรับ imperative ตามโจทย์ข้างบน เราก็จะเขียนโค้ดได้ประมาณนี้แหละ แต่เดี๋ยวสิ อันนี้มันไม่ใช่การสร้างลิสต์ซะหน่อย นี่มันแค่ปริ้นค่าเลขออกมาธรรมดานะ!?

แต่ลองคิดดูนะ เลขที่เราปริ้นออกมาพวกนั้น คือตัวเลขที่เราต้องการจะเอามาสร้างเป็นลิสต์นี่นา

ถ้าอย่างนั้นเราเอาสัญลักษณ์ของลสิต์ คือ `[` และ `]` ครอบโค้ดนี้ไป แล้วเอา `print()` ออกจะได้มั้ย แบบนี้...

```js
var list = [
  for(i=0; i<=100; i++){
    if(i % 3 == 0 || i % 5 == 0){
      i
    }
  }
]
```
เอ๊ะ ถามอะไรอย่างนั้น คอมพิวเตอร์มันจะไปเข้าใจได้ยังไงกัน!?

ผู้ที่อ่านอยู่บางคนอาจจะมีความคิดแบบนี้ แล้วก็ต่อด้วยความคิดที่ว่าเมื่อกี้ตอน `range` ฉันก็โดนหลอกไปแล้วรอบนึง ก็จะมีความลังเลว่าถ้ามันไม่ได้แล้วคนเขียนมันจะยกตัวอย่างนี้ขึ้นมาทำไม แล้วมันทำได้จริงๆ เหรอ?

ถูกแล้วครับ! นี่คือขั้นสูงสุดของ List Comprehension เพราะมันสามารถเข้าใจลิสต์ของเราได้ยังไงล่ะ! (เฉพาะภาษาที่มีฟีเจอร์นี้นะ)

**คอนเซ็ปของ List Comprehension ก็คือเราสามารถวนลูปข้างในลิสต์เพื่อกำหนดค่าให้แต่ละ element ของลิสต์ได้เลย**

โดยภาษาที่จะยกมาสอนคือ Haskell (อีกแล้ว) และ Python กับ Dart (ภาษา Python น่าจะรู้จักกันดีอยู่แล้ว / ภาษา Dart เป็นภาษาสำหรับเขียนแอพแบบ cross-platform หากสนใจตามไปอ่านได้ที่ [Dart 101: ทำความรู้จักภาษาDartฉบับโปรแกรมเมอร์](https://dev.to/centrilliontech/dart-101-dart-l4l) .. ขายของเฉยเลย ฮา)

มาเริ่มกันด้วย Haskell กับสไตล์ภาษา FP แท้ๆ กันก่อน

โครงสร้าง Loop สำหรับทำ List Comprehension จะแบ่งได้หลักๆ 3 ส่วน นั่นคือ..
- **Output Item**: ผลลัพธ์ที่ต้องการ
- **Loop**: ลูปที่จะวนสร้างไอเทม
- **Condition**: เงื่อนไขว่าต้องการไอเทมไหนบ้าง

![](https://1.bp.blogspot.com/-d-XE53tXrFo/XrK13vP5t3I/AAAAAAAAST0/ilHr0rbEj0AcQOh9e4KWo0V3l2F7Hjw_QCNcBGAsYHQ/s1600/fp-04-l1.png)

เช่นตัวอย่างข้างบนคือสร้างลิสต์ของตัวเลขตั้งแต่ 1-10 แต่เลือกเฉพาะตัวที่เป็นเลขคู่ (หาร 2 ลงตัว) เท่านั้น
```
output:
[2,4,6,8,10]
```

แต่ใน haskell เรายังสามารถกำหนดลิสต์ขึ้นมาจากลิสต์อื่นๆ กี่ตัวก็ได้ แถมมีเงื่อนไขกี่ตัวก็ได้เช่นกัน

เช่น ต้องการหาว่าตัวเลขตั้งแต่ 1-10 มีกี่คู่ที่สามารถบวกกันได้ 10 พอดี ก็เขียนลิสต์ได้แบบนี้

![](https://2.bp.blogspot.com/-BZH2aS5_aSo/XrK0gpyDBPI/AAAAAAAASTo/U7s9Hc05QZMeWCBnhzw7pvnapMOiyC8AgCNcBGAsYHQ/s1600/fp-04-l2.png)

```
output:
[(5,5),(6,4),(7,3),(8,2),(9,1)]
```

อธิบาย: 
1. เวลาอ่านโค้ดพวกนี้ให้เริ่มจาก Loop -> Condition -> Output นะ จะทำให้เข้าใจได้ง่ายขึ้น
2. เริ่มจากการบอกว่าเราจะสร้างลิสต์จาก ลิสต์ 2 ตัวซึ่งประกอบด้วยตัวเลข 1-10 ทั้งคู่ 
3. ดึงเลขแต่ละตัวออกมาจากลิสต์ 2 ตัวนั้น ขอเรียกว่า `a` กับ `b`
4. เลือกเฉพาะตัวที่ `a` + `b` ได้ 10
5. เพื่อป้องกันได้คู่ซ้ำ เช่น (6,4) กับ (4,6) เลยใส่ลงไปอีกเงื่อนไขหนึ่งคือ `b <= a` (ทำให้ผลออกมาแต่ (6,4) ไงล่ะ)
6. สุดท้าย คำตอบจัดอยู่ในรูป pair(a,b)

สังเกตว่าการเขียนแบบนี้เป็นการเขียนสไตล์ declarative คือการกำหนดเงื่อนไขเฉยๆ เลย ไม่มีการนำคำสั่งมาเรียกกันเป็น logic แบบ imperative เลย เวลาเห็นโค้ดก็เข้าใจกว่าการเขียนลูปเยอะมาก เพราะแทบจะเป็นภาษาคนแล้ว (ใครยังไม่คล่อง อาจจะต้องฝึกซักพัก)

### Cartesian Product
สำหรับการทำ List Comprehension ถ้าลิสต์ต้นฉบับ (Source List) มีมากกว่าหนึ่งตัว มันจะจับคู่ไอเทมแต่ละตัวเข้าด้วยกัน แบบกระจายทุกความเป็นไปได้ หรือที่เราเรียกว่า ผลคูณคาร์เทเชียน (Cartesion Product)

![](https://2.bp.blogspot.com/-LdDrqshmHIM/XrK0fgIhcPI/AAAAAAAASTg/1S0RmhE7R1YuVY4DBwNivOY227gFb52CgCNcBGAsYHQ/s1600/fp-04-4.png)

ขอพักภาษา haskell ไว้แค่นี้ก่อน ลองกลับมาดูภาษาที่เราคุ้นเคยกันบ้าง
```py
# python
[ x for x in range(1, 10+1) if x % 2 == 0 ]
  │  │                       │
  │  └────── loop            └─ condition
  └─ output
```

```dart
//dart
[ for(var x=0; x<=10; x++) if(x % 2 == 0) x ]
  │                        │              │
  └────── loop             └─ condition   output
```
และนั่นละครับท่านผู้อ่าน! ความมึนงงของแต่ละภาษามันก็เกิดขึ้นตรงนี้ เพราะลำดับ output, loop, condition ของแต่ละภาษามันดันเรียงไม่เหมือนกัน! (จุดนี้ก็ตัวใครตัวมันละกันนะ คอนเซ็ปมันเหมือนกัน แต่เขียนไม่เหมือนกัน จำกันเองนะ)

## Quiz!

ก่อนจะจบบท ลองมาทำโจทย์ประยุกต์ใช้งาน List Comprehension ในการหาคำตอบกันหน่อย

โจทย์ก็ง่ายๆ ไม่มีอะไรมาก
![](https://3.bp.blogspot.com/-tso3vJhUZuE/XrLq1gADDvI/AAAAAAAASUU/SF3k-lvFkNwuhS2WpjoOekT4mfHUGp9OgCNcBGAsYHQ/s1600/fp-04-6.png)
กำหนดให้แบบรูปข้างบนนี่แหละ จงหาค่าของ rabbit, dog, และ pig

*โจทย์พวกนี้หลายๆ คนเห็นมักจะคันไม้คันมืออยากหาคำตอบ ... เอาจริงๆ โจทย์พวกนี้คือโจทย์ สมการหลายตัวแปรธรรมดาเลย แต่ถ้าเปลี่ยนเป็นตัวแปร x, y, z ดูนะ จะไม่มีใครอยากเล่นเลย*

```
x + x + x = 9
x + x - y = 7
x * z = 20
```
หมดความน่าเล่นไปในทันใด (ฮา)

เอาล่ะ คุณผู้อ่านจะลองคิดเล่นๆ ด้วยตัวเองก่อนก็ได้นะว่าคำตอบเป็นเท่าไหร่ จากนั้นลองไปดูว่า List Comprehension หาคำตอบพวกนี้ได้ยังไง

ยกตัวอย่างเป็นภาษา Dart นะ
```dart
answers = [ 
    for(var rabbit = 1; rabbit <= 20; rabbit++)
    for(var dog = 1; dog <= 20; dog++)
    for(var pig = 1; pig <= 20; pig++)
      if(
        rabbit + rabbit + rabbit == 9 &&
        dog + dog -rabbit == 7 &&
        dog * pig == 20
      )
        [rabbit, dog, pig]
  ];

print(answers.first);
```
```
output:
[3, 5, 4]
```
อธิบาย
1. เริ่มจากเราต้องกะเอาว่า คำตอบของ rabbit dog pig 3 ตัวนี้น่าจะอยู่ในช่วง 1-20 ไม่เกินนี้แน่ๆ (เดาเอาจากตัวเลขในโจทย์)
2. เราก็เขียนลูปสร้างคู่คำตอบทุกความเป็นไปได้ เริ่มตั้งแต่กำหนดให้สัตว์ทุกตัวเป็น 1-20 แล้วลูปซ้อนๆ กัน
3. สร้างเงื่อนไขตามโจทย์เลย ถ้าคู่คำตอบในรอบนั้นตรงเงื่อนไข ก็เก็บเอาไว้ในลิสต์
4. สังเกตว่าคำตอบจะออกมาเป็น List เสมอ แต่สมการนี้เราต้องการคำตอบเดียว เลยใช้ `first` ในการดึงเฉพาะตัวแรกออกมาก็พอ
5. ได้คำตอบเป็น rabbit=3 dog=5 pig=4 (ทำไมหมูเบากว่าหมา ไม่ต้องสงสัยนะ ฮา)