Immutable ตัวแปรที่เปลี่ยนค่าไม่ได้!
Tags: beginners, programming, functional, oop
Published: 2020-05-16 14:36:54

ในบทนี้เราจะได้รู้จักกับศัพท์ 2 คำคือ **"Mutable"** ซึ่งแปลว่า เปลี่ยนแปลงได้, ไม่แน่นอน, แปรผัน แต่เมื่อมันมาอยู่ในเรื่องการเขียนโปรแกรม มันจะหมายถึงชนิดของตัวแปรที่สามารถเปลี่ยนแปลงค่าได้นั่นเอง

อ่านมาถึงตรงนี้ ถ้าใครเพิ่งหักเขียนโปรแกรมอาจจะมีการ *เอ๊ะ!?* ขึ้นมาในใจได้ ว่าตัวแปรหรือ variable เนี่ยมันก็ต้องเปลี่ยนค่าได้อยู่แล้วไม่ใช่เหรอยังไง ถ้าเปลี่ยนค่าไม่ได้เราจะคำนวณผลได้ยังไง

ประเด็นก็คือมันมีตัวแปรอีกแบบหนึ่งที่ตรงข้ามกับ mutable นั่นคือตัวแปรประเภท **"Immutable"** (ภาษาอังกฤษ เติม `im-` เข้าไปข้ามหน้าส่วนใหญ่จะทำให้ความหมายกลับเป็นตรงกันข้าม) แปลว่า**เปลี่ยนแปลงไม่ได้**ยังไงล่ะ

ตัวแปรที่เป็น immutable นั้นจะกำหนดค่าได้ครั้งเดียวเท่านั้น หลังจากนั้นตัวแปรจะอยู่ในสถานะ **Read-Only** ไปจนกว่าตัวแปรนั้นจะถูกทำลาย

ในแต่ละภาษามีอาจจะมีการกำหนดว่าตัวแปรไหนเป็น immutable ที่ต่างกัน เช่น

|Language|Mutable|Immutable|
|---|---|---|
| JavaScript | `let x` หรือ `var x` | `const x` |
| Java | `int x` | `final int x` |
| C# | `int x` | `readonly int x` |
| Kotlin | `var x` | `val x` |
| Swift | `var x` | `let x` |
| Dart | `var x` | `final x` |

(ใครต้องเขียนหลายภาษาบอกเลยว่ามีงงแน่นอน ฮา)

 > **คำเตือน!** ในบางภาษา, ตัวแปร immutalbe นั้นอาจจะถูกแบ่งเป็น 2 ประเภทย่อยอีก นั่นคือ immutable แบบ runtime (ค่าสร้างตอนรันโปรแกรม จะคำนวณมาจากค่าอื่นอีกทีก็ได้) และ compile-time (ค่าสร้างตั้งแต่ตอนคอมไพล์ ต้องเป็นค่า constant เท่านั้น เช่น PI = 3.14) ดังนั้นใช้งานภาษาอะไรอยู่ อ่านdocของภาษานั้นดีๆ ด้วยล่ะ!



## ทำไมต้อง Immutable ?

ในมุมมองโปรแกรมเมอร์ทั่วไป เราสามารถเปลี่ยนแปลงค่าตัวแปรยังไงก็ได้
```js
var x = 100
x = x + 1
```
เช่น สร้าง `x` มาแล้ว อยากเพิ่มค่าให้ `x` อีก 1 ก็สั่ง `+1` เข้าไป (เคสนี้เจอบ่อยเวลาสร้างลูป ตรง `i++` ยังไงล่ะ)

ในเคสนี้ ถ้าเราเปลี่ยนค่าตัวแปรไม่ได้ เราก็ต้องเขียนโค้ดแบบนี้แทน
```js
var x = 100
var y = x + 1

//หรือ
var x = 100
var y = add(x, 1)
```
ซึ่งดูแล้วหาประโยชน์อะไรไม่ได้เลย! โอเค นี่คงไม่ใช่ตัวอย่างที่ดีนักสำหรับการใช้ immutable งั้นเราลองมาดูตัวอย่าง use case ที่เราควรกำหนดตัวแปรเป็น immutable กันดีกว่า..

### 1.สร้างตัวแปรค่าคงที่ กันเผลอไปเปลี่ยนโดนไม่ตั้งใจ

```java
class ParkingFee {
    int ratePerHour = 10
    ...
}
```
สมมุติเราสร้างตัวแปรซึ่งเป็นค่าคงที่ ไม่สามารถเปลี่ยนแปลงได้ขึ้นมาตัวหนึ่ง

เราอาจจะบอกว่าไม่เห็นต้องประกาศระบุ **immutable** ขนาดนั้นเลยก็ได้นี่นา เราสร้างตัวแปรขึ้นมา เราก็จำได้อยู่แล้วว่าตัวแปรนี้มันห้ามเปลี่ยนค่า ก็อย่าซนไปเปลี่ยนค่ามันสิ ก็เท่านั้นเอง

ใช่แล้วครับ **ถ้าคุณจำได้น่ะ ก็ไม่มีปัญหาหรอก** แล้วถ้าคุณลืมล่ะ เช่นไม่ได้แตะโปรเจคนี้มาหลายๆ เดือนแล้ว หรือคุณอาจจะทำงานเป็นทีมกับโปรแกรมเมอร์คนอื่น เขาจะรู้มั้ยว่าตัวแปรนี้ห้ามเปลี่ยนค่า?

เดี๋ยวนี้โปรแกรมเมอร์ใช้งาน IDE กันเยอะ การมีโค้ดเด้งๆ ขึ้นมาเป็นเรื่องปกติ บางคนเพื่อนๆ คุณอาจจะไม่รู้ แต่เมื่อมีโค้ดเด้งขึ้นมาให้ ฉันก็นึกว่ามันใช้งานได้น่ะสิ!

```java
class ParkingFee {
    final int ratePerHour = 10
    ...
}
```

ดังนั้น ทางที่ดีเราควรกำหนดความเป็น immutable ให้ตัวแปรที่ไม่ต้องการให้เปลี่ยนค่าลงไปตรงๆ เลยจะดีกว่า

### 2.ลดการเกิดบั๊กจากการเปลี่ยนค่าตัวแปรโดยไม่ตั้งใจ

ลองดูโค้ดข้างล่างนี้หน่อย แล้วลองเดาดูว่า `s.score` ที่บรรทัดสุดท้ายจะมีค่าเป็นเท่าไหร่?

```js
Student s = Student()
s.name = "Ann"
s.score = 50

displayStudentScore(s)

s.score = ?
```

หลายคนน่าจะเดาว่ามันจะได้ **50** เนื่องจากเรากำหนดค่า `s.score = 50` ตั้งแต่ข้างบน ถึงแม้มันจะผ่านฟังก์ชันที่เอาค่าเราไปปริ้น แต่ค่าก็ควรเป็น 50 เท่าเดิมสิ?

ใช่ครับ ค่ามันควรจะเป็น 50 เท่าเดิม ถ้าฟังก์ชันปริ้นมันทำอะไรข้างในประมาณนี้

```js
function displayStudentScore(student){
    //Read-Only
    print(`${student.name} get score ${student.score}.`)
}
```

แต่ถ้ามันมี requirement เข้ามาเพิ่มว่าเราต้องการ **"เพิ่มคะแนนให้นักเรียนทุกคน คนละ 10 คะแนน"** ล่ะ ... พอดีเหลือเกิ๊น! ที่วันนั้นคุณไม่ว่าง เลยเป็นเพื่อนคุณที่เข้ามาแก้โค้ดนี้ให้แทน ซึ่งเขาก็หาไปหามา แล้วก็เจอว่า ถ้าอยากให้คะแนนเพิ่ม งั้นก็บวกมันเพิ่มไปตรงนี้ละกัน 

แบบนี้...

```js
function displayStudentScore(student){
    
    //Mutable!: bonus score 10 point
    student.score += 10

    print(`${student.name} get score ${student.score}.`)
}
```
เนื่องจาก object ทุกตัวเป็น reference type ดังนั้นการแก้ค่าของอ็อบเจคในฟังก์ชันก็จะส่งผลออกมาถึงด้านนอกด้วย นั่นแหละ ทำให้ค่า `s.score` ของเราอาจจะไม่ใช่ 50 แล้วก็ได้**เมื่อมันโดนส่งค่าผ่านฟังก์ชันเข้าไป** 

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

### 3.Shared Memory

หัวข้อนี้ไม่ได้เป็นประโยชน์ทำให้เราเขียนโค้ดดีขึ้น แต่ทำให้เราประหยัดการใช้เมโมรี่มากขึ้น

นั่นคือถ้าเรามีตัวแปร immutable อยู่ แล้วเราสร้างตัวแปรใหม่ขึ้นมาอีกตัวโดยให้มันถือตัวแปร immutable ของเราไว้ด้วย บางภาษาที่ตัว compiler ฉลาดๆ หน่อยจะมองออกว่าไม่ต้อง "copy" ตัวแปรออกมาใหม่อีกตัว สามารถใช้งานแชร์กันได้เลย เพราะถึงยังไงตัวแปรพวกนี้ก็เปลี่ยนค่าไม่ได้อยู่แล้ว

![](https://4.bp.blogspot.com/-klqw6-f20Mg/Xr7Fs_wkgXI/AAAAAAAASXo/MoFW4hAke6MDviVKPqzGNczAkUsb6_b-ACNcBGAsYHQ/s1600/fp-05-7.png)

หรืออีกตัวอย่างหนึ่ง เช่นคลาส `String` ในภาษา Java (String ใน Java เป็นคลาสแบบ immutable นะ)

ถ้าเรากำหนดค่าตัวแปรขึ้นมา 2 ตัวในหัวเราจะคิดว่ามันต้องสร้าง instance ขึ้นมา 2 ชุด แต่ด้วยความเป็น immutable นั้นทำให้คอมไพเลอร์จะมีการเช็กว่าถ้า instance 2 ตัวนั้นมีค่าเหมือนกัน มันจะจัดตัวแปร 2 ตัวนั้นแชร์ค่ากันทันที
![](https://2.bp.blogspot.com/-zY5aOI6_xms/Xr7HnQYlh2I/AAAAAAAASX0/Q5YQdZ2-5lMFlm1GI9gULZmsKq_7TnCpACNcBGAsYHQ/s1600/fp-05-8.png)

*ทำการทดลองได้ด้วยลองสร้าง String 2ตัวที่มีค่าเหมือนกัน แล้วลองเช็กว่ามันเป็นตัวเดียวกันมั้ยด้วย `==` ที่ปกติแล้วจะได้ค่า false เสมอ (ปกติ Java ต้องเทียบสตริงด้วย `equals()` เท่านั้น)*



> ในตัวอย่างต่อไปขออธิบายด้วยภาษา Java นะ .. เพราะมันเป็นภาษาที่เคร่งแนวคิด OOP ประมาณนึงเลย

## OOP และการใช้ getter/setter

ตามปกติแล้ว สำหรับคนที่เรียน OOP มาจะมีคอนเซ็ปหนึ่งที่เราต้องทำกันตลอดเวลา นั่นคือ getter และ setter

- เพราะตามหลัก OOP เราจะต้องป้องกันไม่ให้คนอื่นเข้ามายุ่งกับตัวแปรภายในของเรา (กันคนแอบเปลี่ยนค่า) = เราเลยมักเซ็ต properties ทั้งหมดให้เป็น `private`
- แล้วก็มีปัญหาตามมา นั่นคือถ้าเราล็อกไม่ให้คนอื่นเข้ามาใช้ค่าภายใน แล้วมันจะติดต่อกับอ็อบเจคตัวอื่นได้ยังไงล่ะ
- วิธีแก้คือสร้างเมธอดขึ้นมา เป็นตัวกลางในการเข้าถึงตัวแปรระหว่างคลาสเรากับคลาสอื่นๆ

```java
class Student {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public int getScore() {
        return score;
    }
}
```

เช่นตัวอย่างโค้ดข้างบน เรามี properties 2 ตัวคือ `name` และ `score` เราก็ต้องสร้างเมธอดที่เรียกว่า getter (เอาไว้ดึงค่าออกมา) และ setter (เอาไว้เซ็ตค่ากลับเข้าไป)

คนเรียน OOP ทุกคนจะต้องเขียนฟังก์ชันอันน่าเบื่อพวกนี้เยอะมาก เขียนจนชิน และโดยส่วนใหญ่จะเลิกเขียนเองแล้วใช้ IDE สร้างให้ก็ตาม (ฮา)

**แต่จริงๆ แล้วนี่เป็นวิธีที่ไม่ดีเท่าไหร่!** (อ้าว! แล้วฉันเรียนอะไรมาเนี่ย)

ก็เพราะการทำแบบนี้ให้ตัวอ็อบเจคของเรามีโอกาสเปลี่ยน state ค่าตัวแปรได้หลายจุดมากๆ หมายถึงถ้าเราส่งอ็อบเจคเราข้ามฟังก์ชันไปข้ามฟังก์ชันมา มีโอกาสที่อยู่ๆ ค่าของอ็อบเจคเราจะถูกเปลี่ยนโดยใครก็ไม่รู้ จับคนร้ายไม่ได้ด้วย (จับได้แหละ แต่ยาก!)

ดังนั้น FP จึงแนะนำให้คุณสร้างอ็อบเจคแบบ immutable ซะ
```java
class Student {
    public final String name;
    public final int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
}
```

เราประกาศ `final` ที่ properties ทุกตัวเลย ทีนี้คนอื่นก็จะแก้ค่าพวกนี้ไม่ได้แล้ว

สังเกตต่ออีกอย่างคือเราเอา getter/setter ออกไปด้วย แล้วเปิดให้ properties เป็น `public` แทน ... ก็ไม่เห็นต้องกลัวใครเปลี่ยนค่าแล้วไง มันแก้ค่าไม่ได้อยู่แล้ว

> หลังๆ มานี่เวลาเราสร้าง class เราชอบสร้างในรูปแบบนี้เสมอเลยนะ เลิกเขียนกันทีกับ getter/setter โค้ดดูสะอาดขึ้น แถมเขียนสั้นลงและลดบั๊กอีกด้วย

แต่ก็ไม่จำเป็นนะว่าจะต้องรับค่าเข้ามาตรงๆ เราอาจจะรับค่าเข้ามาแล้วโปรเซสอะไรบางอย่างก่อนก็ยังไง
```java
class Employee {
    public final String email;
    public final String name;

    public Employee(String firstname, String lastname) {
        this.name = firstname + " " + lastname;
        this.email = firstname + "." + lastname.substring(0,2) + "@tamemo.com"
    }
}
```

### Builder Pattern
ปัญหาจริงๆ ของการสร้างอ็อบเจคให้เป็น immutable คือเวลาเราจะแก้ค่าอะไร เราจะต้องสร้างอ็อบเจคใหม่ขึ้นมาเลย

เช่นต้องการ `+10` คะแนนให้ Student หนึ่งคน
```java
Student stu1 = new Student("Ann", 50);
Student stu2 = new Student(stu1.name, stu1.score + 10);
```
ก็ต้องสร้างอ็อบเจคตัวใหม่ แล้ว copy ค่าจากอ็อบเจคตัวเก่ามาใส่ตัวใหม่ให้หมดเลย

*เอาจริงมันก็ไม่ใช่ปัญหาหรอก แค่มันเขียนยาววววขึ้น 55*

ดังนั้นเราอาจจะเอา **"Builder Pattern"** มาช่วย ก็จะทำให้การ copy ค่าทำได้ง่ายขึ้น

> **Builder** ซึ่งเป็นหนึ่งใน Design Pattern ซึ่งยังไม่ขอพูดถึงในบทนี้ ในอนาคตจะมาเขียนเกี๋ยวกับเรื่องนี้ต่ออีกทีหนึ่งนะ

```java
class Data {
    int x, y, z, p, q, r ;
}

Data data1 = new Data(1, 2, 3, 4, 5, 6);

// Standard
Data data2 = new Data(
    10, data1.y + 100, data1.z * 2, data1.p, data1.q, data1.r
);

// Builder Pattern
Data data2 = new Data.Builder()
    .from(data1)
    .setX(10)
    .setY(data1.y + 100)
    .setZ(data1.z * 2)
    .build();
}
```

## สรุป
การทำให้ตัวแปรเปลี่ยนค่าไม่ได้ อาจจะทำให้คนที่เพิ่งเรียนเขียนโปรแกรมแปลกใจกับแนวคิดนี้ (เพราะที่มหาลัยส่วนใหญ่จะไม่ได้สอนข้อเสียของ OOP) แต่เชื่อเถอะว่าการที่ตัวแปรเปลี่ยน state ได้น่ะ ทำให้เกิดบั๊กมากมายตามมา 

ไม่อย่างนั้นภาษายุคใหม่ที่ออกมาแต่ละตัว คงไม่สร้างชนิดตัวแปรแบบ immutable ออกมาให้เราใช้กันหรอก บางภาษาถ้าตัวแปรนั้นเป็น mutable แล้วคอมไพเลอร์เช็กเจอว่าเราไม่ได้เปลี่ยนค่าอะไร บางทีเจอ warning ด้วยซ้ำ (มันจะบอกว่าเปลี่ยนเป็น immutable เถอะ)

ถ้าเป็นไปได้พยายามใช้ตัวแปรแบบ immutable ทั้งหมดนะครับ