10110 การทำ Flag ด้วย Bitset  ใน Golang
Tags: go, programming
Published: 2020-12-05 12:45:58

โปรแกรมเมอร์รุ่นใหม่ๆ ที่ไม่คุ้นกับภาษา Go (หรือภาษาเก่าๆอื่นๆ) อาจจะสงสัยว่า ไอ้ parameter ที่มัน `OR` ได้นี่มันคืออะหยังหนอ
```go
	os.OpenFile("pathtofile", os.O_APPEND|os.O_RDWR, 0755)
                                      ^^^ ไอ้นี่
	log.SetFlags(log.Llongfile | log.LUTC)
                         ^^^ ไอ้พวกนี้ด้วย
```

มันคือ Flags นั่นเองครับ underhood มันคือ int ธรรมดาแต่ถูกจัดการด้วย bitwise operator ซึ่งจะเป็นหัวข้อของ blog นี้


# 🌟 TL;DR สรุปก่อนไปเลย
[ตัวอย่างสุดท้าย ใน Playground](https://play.golang.org/p/lH5P1ncioFG)
Bitset Flag จะทำให้เราสามารถ pass enum parameter ได้มากกว่า 1 parameter โดยไม่ต้องแก้ไข signature ของ function เพิ่ม ทำให้สามารถส่ง enum เข้าไปใน function ได้โดยไม่ต้องใช้ slice

การทำ Flag ใน Golang เหมือนกับการทำ Flag ในภาษาอื่นๆ แต่ด้วยความสามารถของ `iota` ทำให้การทำ Flag ง่ายขึ้นนิดนึง
```golang

type flag int

const (
	flagWithA flag = 1 << iota
	flagWithB
	flagWithC
	flagWithD
)

func (f flag) has(v flag) bool {
	return f&v != 0
}

func print(flags flag) {
	fmt.Println(
		flags.has(flagWithA),
		flags.has(flagWithB),
		flags.has(flagWithC),
		flags.has(flagWithD),
	)
}
func ExampleBitsetFlag() {
	print(flagWithA | flagWithD)
	// output: true false false true
}
```

<hr />
# Flag คืออะไร
การพัฒนา application ด้วย Golang คงจะเคยเจอการใช้ Flag กันผ่านๆมาแล้ว โดยเฉพาะการ `OpenFile` 
```go
os.OpenFile("somedir", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0655)
                        ^^^^ ตรงนี้
```
ใน parameter ที่ 2 จะเห็นว่าเราสามารถ pass parameter ไปได้มากกว่า 1 ตัวทั้งๆที่ parameter ตรงนี้กำหนดไว้แค่ int อย่างเดียวใน signature

```go
// OpenFile is the generalized open call; most users will use Open
// or Create instead. It opens the named file with specified flag
// (O_RDONLY etc.). If the file does not exist, and the O_CREATE flag
// is passed, it is created with mode perm (before umask). If successful,
// methods on the returned File can be used for I/O.
// If there is an error, it will be of type *PathError.
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
```

หลายคนที่ย้ายมาจากภาษาอื่นๆ อาจจะไม่คุ้นตากับการใช้ Flag แบบนี้สักเท่าไหร่ (เนื่องจากภาษาอื่นก็มี pattern อื่นๆในการ pass value แบบนี้แทนแล้ว)

แต่เนื่องจาก Golang พัฒนามากับความเรียบง่าย เราเลยยังเห็นการใช้ Bitset และ bitwise operator เพื่อทำ flag parameter อยู่



## Bitwise Operator คืออะไร?
ในภาษาต่างๆ เราอาจจะเคยเห็นการทำ Bitwise Operator กันมาบ้าง ถ้าเป็น Developer ใหม่ๆอาจจะไม่คุ้นชินตาเท่าไหร่ (ถ้าไม่ใช่ภาษาอย่าง C หรือ C++ หรือตอนเรียนมหาลัย)

ในบทความนี้มี 3 bitwise operators ที่เราจะใช้งานกันคือ 
- `<<` left shift
- `&` AND
- `|` OR

### Left shift operator
เครื่องหมาย `<<` หมายถึงการขยับ bit value ไปด้านซ้าย (และกลับด้านกัน `>>` right shift คือขยับไปขวา)
```go
package main
import "fmt"
func ExampleBitwise() {
	var n = 2 << 8
	fmt.Println(n)
	fmt.Println(n >> 2)
	// output: 512
	// 128
}

```

ในตัวอย่างจะเห็นว่ามีการ `assign left shift int(2)` ให้กับตัวแปร `n` สิ่งที่ได้จาก expression นี้คือเราทำการขยับ bit ปัจจุบันไปด้านซ้าย จำนวน 8 ตัวให้กับ int(2)

|Decimal Number|Binary Number|
|---|---|
| 2 | 10 ( มี 0 จำนวน 1 ตัว ) |
| 512 | 1000000000 (มี 0 จำนวน 9 ตัว ) |

### AND และ OR operator
สำหรับการทำ `AND` operator เราจะได้ผลลัพธ์ออกมาเป็น bitset ที่ตรงกัน สำหรับ 2 values (intersect)

และ `OR` operator เราจะได้ผลลัพธ์ของ bit position ที่มีค่า (union)

ตัวอย่างเช่น
```go
func ExampleBitsetAnd() {
	var n = 4   // this is 0100
	var nn = 12 // this is 1100
	fmt.Printf("%b %b", n&nn, n|nn)
	// output: 100 1100
}
```

## สร้าง Enum ด้วยค่าของ bit set
จาก Operator ที่ได้เกริ่นมา จะทำให้เราสามารถเอาหลักการ bitset มาใช้แทน value ของ flags ได้ด้วยการให้แต่ละ Bit บ่งบอกถึงการเปิดและปิดของ flag ต่างๆ

ยกตัวอย่างเช่น 
Function `echoHello` สามารถ echo `Hello` ในภาษาต่างๆได้
```
       0000  // 4 bits
       ^ first bit represents echo in English
        ^ second bit represents echo in Thai
         ^ third bit represents echo in Japanese
          ^ last bit represents echo in French
```

เราก็จะได้ enum ออกมาเป็นแบบนี้
```go
const (
	EchoEN = 1 // 0001
	EchoTH = 2 // 0010
	EchoJP = 4 // 0100
	EchoFR = 8 // 1000
)

func ExampleEchoHello() {
    // ส่ง 1011 เข้าไป
    echoHello(EchoEN | EchoTH | EchoFR)
    // output: Hello
    // สวัสดี
    // Bonjour
}

func echoHello(flags int) {  // flags input คือ 1011
   if flags&EchoEN != 0 { // ผลลัพธ์ของการ 1011 AND 0001 คือ 0001
      fmt.Println("Hello")
   }
   if flags&EchoTH != 0 { // ผลลัพธ์ของการ 1011 AND 0010 คือ 0010
      fmt.Println("สวัสดี")
   }
   // .... do the rest of logic
}


```

จากตัวอย่างชุดโปรแกรมด้านบน จะทำให้เราเห็นว่า เราสามารถใช้ bitset เพื่อแทน ค่าของ boolean หลายๆตัวได้ โดยไม่ต้องใช้ slice

## ใช้ `iota` และ เปลี่ยนจาก int เป็น type ใหม่เพื่อให้ bitset ใช้งานง่ายขึ้น
จาก code ด้านบนจะเป็นการเขียนโปรแกรมตรงๆ ไม่ได้ใช้คุณสมบัติอะไรพิเศษจาก Golang ตอนนี้เราจะมาใช้คุณสมบัติของ Golang ให้ bitset flag เราใช้งานง่ายขึ้น

เริ่มจาก ประกาศ enum และ left shift โดยใช้ `iota` (คีย์เวิร์ดสำหรับทำ successive integer หรือเลขที่เพิ่มขึ้นเรื่อยๆ [อธิบาย successive integer](https://www.mathsisfun.com/definitions/successive.html)
```go
const (
	EchoEN = 1 << iota // 1 << 0 = 0001
	EchoTH             // 1 << 1 = 0010
	EchoJP             // 1 << 2 = 0100
	EchoFR             // 1 << 3 = 1000
)
```

จากนั้น เพื่อทำให้ง่ายต่อการเช็ค bit เราจะทำ type ใหม่สำหรับ Echo Flag และเขียน function เพื่อตรวจว่า bitset นั้นมี enum ที่เราอยากรู้มั้ย

```go
type EchoFlag int
func (e EchoFlag) has(f EchoFlag) bool { 
     // ตัวอย่างถ้า e มีค่า = 12 ( 1100 )
     return e&f != 0 
     // ถ้า f คือ 0001 จะ return false เพราะ 1100 & 0001 = 0000
     // ถ้า f คือ 0100 จะ return true  เพราะ 1100 & 0100 = 0100
}
```

จากนั้นเรามาแก้ให้ `echoHello` เรารับ `EchoFlag` และใช้ `has()` เพื่อเช็ค flag ของ input


```go
func echoHello(flags EchoFlag) {
	if flags.has(EchoEN) {
		fmt.Println("Hello")
	}
	if flags.has(EchoTH) {
		fmt.Println("สวัสดี")
	}
	if flags.has(EchoJP) {
		fmt.Println("こんにちは")
	}
	if flags.has(EchoFR) {
		fmt.Println("Bonjour")
	}
}
```

เสร็จหมดแล้ว! 🎊 🎉

[ตัวอย่าง ใน Playground](https://play.golang.org/p/lH5P1ncioFG)


## แล้วมันเอาไว้ใช้ทำอะไร?
อย่างที่ยกตัวอย่างไปตั้งแต่แรก Native package ของ Golang อย่าง `os` จะใช้ Flag ในการทำ operation ต่างๆ โดยเฉพาะ arguments ที่จะส่งไปหาระบบปฏิบัติการ

![os flag](https://dev-to-uploads.s3.amazonaws.com/i/771tyvpn033jhvp2tfhn.png)

หรือ package `log` ที่ใช้ flag ในการ set output option ของการ log
![log flag](https://dev-to-uploads.s3.amazonaws.com/i/a7r64r76zz1ykniz49sv.png)


นอกจากนี้ผมก็ไม่รู้เหมือนกันว่าเอาไว้ทำอะไรอีก 🤨

จบสวัสดี 🙆‍♂️