Enums và iota trong Go – Các tip và trick cùng code mẫu.

Chú ý: Trong bài hướng dẫn này, đầu tiên các bạn sẽ học về cách sử dụng enums không có iota – Sau đó, các bạn sẽ tìm hiểu về iota và các công cụ hỗ trợ khác.

Enum là gì?

Enum nhóm các hằng số (constant) có liên quan đến nhau thành cùng một kiểu.

Ví dụ:

  • Timezones: EST, CST…
  • T-shirt Sizes : Small, Medium, Large
  • Server Statuses: Unknown, Running, Stopped, Resumed

 

Tại sao chúng ta cần sử dụng enum?

  • Nhóm và xác định một số giá trị có liên quan đến nhau
  • Chia sẻ những hành vi chung
  • Tránh sử dụng giá trị không hợp lệ
  • Tăng khả năng đọc hiểu và bảo trì code

 

Làm thế nào để tạo một enum trong Go?

Ví dụ, tạo một enum cho các ngày trong tuần.

Mỗi hằng số sẽ có cùng một kiểu: kiểu Weekday

♦ Bước 1: Khai báo một kiểu dữ liệu tùy chỉnh có tên là Weekday

Weekday sẽ thống nhất các hằng số enum của chúng ta dưới một kiểu chung.

type Weekday int

♦ Bước 2: Khai báo các hằng số liên quan đến Weekday

Chỉ định cho chúng các giá trị số khác nhau, như vậy chúng sẽ không bị xung đột.

const (
   Sunday    Weekday = 0
   Monday    Weekday = 1
   Tuesday   Weekday = 2
   Wednesday Weekday = 3
   Thursday  Weekday = 4
   Friday    Weekday = 5
   Saturday  Weekday = 6
)
// hiển thị số 0
fmt.Println(Sunday)
// hiển thị số 6
fmt.Println(Saturday)

 

Tạo các hành vi chung cho enum

Chúng ta thêm vào các phương thức (methods) cho một kiểu để xác định hành vi của nó.

Các phương thức được đính kèm sẽ là các phần không thể tách rời của Weekday và được chia sẻ với các hằng số của Weekday.

♦ Phương thức String():

func (day Weekday) String() string {
    // khai báo một mảng các string
    // toán tử ... để đếm số phần tử
    // số phần tử của mảng là (7)
    names := [...]string{
        "Sunday", 
        "Monday", 
        "Tuesday", 
        "Wednesday",
        "Thursday", 
        "Friday", 
        "Saturday"}

    // `day`: là một trong các giá trị của hằng số Weekday.    
    // Nếu hằng số là Sunday, thì day có giá trị là 0.
    // Bắt lỗi trong trường hợp `day` nằm ngoài khoảng của Weekday
    if day < Sunday || day > Saturday {
      return "Unknown"
    }
    // trả về tên của 1 hằng số Weekday từ mảng names bên trên
    return names[day]
}

Let’s try:

fmt.Printf("Which day it is? %s\n", Sunday)
// output: Which day it is? Sunday

Chạy code trên playground

Chúng ta đã thêm phương thức String() cho enum Weekday. Thử chạy code để hiểu rõ hơn.

♦ Phương thức Weekend():

func (day Weekday) Weekend() bool {
    switch day {
    // Nếu day là ngày cuối tuần:
    case Sunday, Saturday:
        return true
    // Nếu day là ngày trong tuần:
    default:
        return false
    }
}

Convention: Thay vì đặt tên là IsWeekend, chúng ta chỉ đặt là Weekend.

Let’s try:

fmt.Printf("Is Saturday a weekend day? %t\n", Saturday.Weekend())
// output: Is Saturday a weekend day? true

Chạy code trên playground

Chúng ta đã thêm vào phương thức Weekend cho kiểu Weekday. Thử chạy code.

 

Tự động tạo tên

Các bạn có thể sử dụng Stringer của Rob Pike để tự động tạo phương thức String.

# install the stringer tool
go get -u -a golang.org/x/tools/cmd/stringer
# grab the code from here 
# and save it in weekday.go
# inside your $GOPATH/src/enums
# then, run stringer to create 
# enum names automatically
stringer -type Weekday weekdays.go

Bạn cũng có thể sử dụng một số tool khác như: jsonenums (viết bởi Francesc Campoy) hoặc enumer (viết bởi Álvaro López).

 

Tự động hóa các giá trị enum với iota

  • Là một bộ đếm số phổ quát bắt đầu từ 0
  • Chỉ được sử dụng với các khai báo constant
  • Có thể bị che khuất, nhưng đừng làm thế

 

Làm thế nào để sử dụng iota?

Biểu thức iota được lặp lại bởi các hằng số cho đến khi có một khai báo kiểu hoặc một phép gán khác xuất hiện.

Mỗi một hằng số là kiểu Weekday và sẽ thực thi biểu thức iota+1 của riêng chúng.

Chạy code trên playground

Để xem iota chạy như thế nào, hãy thử ví dụ trên.

 

Cách thức hoạt động của iota

iota tăng 1 sau mỗi dòng ngoại trừ dòng trống và chú thích.

 

Khi nào không nên sử dụng iota

const (
   RestartMarkerReply     = 110
   ServiceReadyInNMinutes = 120
   CommandOK              = 200
   CommandNotImplemented  = 202
   // ...
)

Không sử dụng iota cho danh sách các giá trị được xác định trước ví dụ như FTP server status codes.

const (
    Fatal = iota
)

Ở trường hợp này ta chỉ gán 0. Nó vô hại nhưng không cần thiết.

 

 

Biểu thức iota và các nguyên tắc

Phần này sẽ giúp bạn hiểu rõ hơn về iota

Biểu thức cơ bản

type Timezone int
const (
    // iota: 0, EST: -5    
    EST Timezone = -(5 + iota)
    
    // iota: 1, CST: -6
    CST
    
    // iota: 2, MST: -7
    MST
    
    // iota: 3, MST: -8
    PST
)

Chạy code trên playground

Bạn có thể sử dụng bất kỳ biểu thức liên tục nào với iota.

 

Resetting iota

// iota reset: it will be 0.
const (
    Zero = iota  // Zero = 0
    One          // One = 1
)

// iota reset: will be 0 again
const (
    Two = iota   // Two = 0
)

// iota: reset
const Three = iota // Three = 0

Chạy code trên playground

Bất cứ khi nào một từ khóa const xuất hiện thì iota reset về 0.

 

Bỏ qua một số giá trị

type Timezone int
const (
    // iota: 0, EST: -5
    EST Timezone = -(5 + iota)

    // _ được gọi là blank identifier
    // iota: 1
    _

    // iota: 2, MST: -7
    MST

    // iota: 3, MST: -8
    PST
)

Chạy code trên playground

Bạn có thể bỏ qua một số dòng với blank identifier _, nhưng iota sẽ tiếp tục tăng.

 

Iota khi chạy qua comment và dòng trống

type Timezone int
const (
    // iota: 0, EST: -5
    EST Timezone = -(5 + iota)

    // Trên 1 comment hoặc 1 dòng trống
    // thì iota sẽ không tăng

    // iota: 1, CST: -6
    CST

    // iota: 2, MST: -7
    MST

    // iota: 3, MST: -8
    PST
)

Chạy code trên playground

Iota sẽ không tăng khi chạy qua các comment hoặc các dòng trống.

 

Sử dụng iota ở giữa

const (
    One   = 1
    Two   = 2

    // Three = 2 + 1 => 3
    // iota in the middle
    Three = iota + 1

    // Four  = 3 + 1 => 4
    Four
)

Chạy code trên playground

Iota có thể được sử dụng trên bất kỳ dòng nào bên trong khai báo const. Ở đây nó là 3 thay vì 0.

 

Nhiều iota trên cùng một dòng

const (
    // Active = 0, Moving = 0,
    // Running = 0
    Active, Moving, Running = iota, iota, iota

    // Passive = 1, Stopped = 1,
    // Stale = 1
    Passive, Stopped, Stale
)

Trong cùng một dòng, tất cả các hằng số sẽ nhận được cùng một giá trị iota.

const (
    // Active = 0, Running = 100
    Active, Running = iota, iota + 100

    // Passive = 1, Stopped = 101
    Passive, Stopped

    // Bạn không thể khai báo như thế này.
    // Biểu thức sau cùng sẽ được lặp lại
    CantDeclare

    // Nhưng bạn có thể reset biểu thức như sau
    Reset = iota

    // Bạn có thể sử dụng biểu thức khác không có iota
    AnyOther = 10
)

Chạy trên playground

Bằng cách sử dụng các biểu thức mới, bạn có thể dừng biểu thức lặp lại trước đó.

 

Lặp lại và hủy biểu thức

const (
    // iota: 0, One: 1 (type: int64)
    One  int64  = iota + 1

    // iota: 1, Two: 2 (type: int64)
    // Two sẽ được khai báo là:
    // Two int64 = iota + 1
    Two

    // iota: 2, Four: 4 (type: int32)
    Four int32  = iota + 2

    // iota: 3, Five: 5 (type: int32)
    // Five sẽ được khai báo là:
    // Five int32 = iota + 2
    Five

    // (type: int)
    Six = 6

    // (type: int)
    // Seven sẽ được khai báo là:
    // Seven = 6
    Seven
)

Biểu thức và kiểu được sử dụng sau cùng sẽ được lặp lại.

Chạy code trên playground

Bạn có thể nhóm các hằng số với các biểu thức khác nhau.

 

Chẵn và lẻ

type Even bool
const (
    // 0 % 2 == 0 ==> Even(true)
    a = Even(iota % 2 == 0)

    // 1 % 2 == 0 ==> Even(false)
    b

    // 2 % 2 == 0 ==> Even(true)
    c

    // 3 % 2 == 0 ==> Even(false)
    d
)

Chạy code trên playground

Toán tử `==` chuyển đổi biểu thức iota thành kiểu bool.

 

Đếm ngược

const (
    max = 10
)

const (
    a = (max - iota) // 10
    b                // 9
    c                // 8
)

Go stdlib text scanner cũng sử dụng patterm này.

Chạy code trên playground

 

Xuất ra bảng chữ cái

const (
    // hàm string sẽ chuyển đổi biểu thức thành chuỗi
    //
    // hoặc nó sẽ gán mã ký tự
    a = string(iota + 'a') // a
    b                      // b
    c                      // c
    d                      // d
    e                      // e
)

Chạy code trên playground

 

Các toán tử thao tác bit

type Month int
const (
    // 1 << 0 ==> 1
    January Month = 1 << iota

    February   // 1 << 1 ==> 2
    March      // 1 << 2 ==> 4
    April      // 1 << 3 ==> 8
    May        // 1 << 4 ==> 16
    June       // ...
    July
    August
    September
    October
    November
    December

    // Chuỗi iota được break tại đây.

    // AllMonths sẽ chỉ có giá trị của tháng được gán,
    // không có giá trị của iota.

    AllMonths = January | February |
        March | April | May | June |
        July | August | September |
        October | November |
        December
)

Với phép bình phương, đoạn code này tạo ra một hằng số bằng tổng cả hằng số trước nó.

Chạy code trên playground

Bạn không cần phải sử dụng iota cho mọi hằng số trong một enum, bạn có thể ghi đè lên các giá trị theo cách thủ công.

 

Cẩn thận với zero-value

type Activity int

const (
    Sleeping = iota
    Walking
    Running
)

func main() {
    var activity Activity
}

0 là zero-value đối với kiểu integer. Vì vậy, bạn không thể biết liệu Activity được khởi tạo hay không; Liệu nó có thực sự ở trạng thái Sleeping không?

Chạy code trên playground

Đoạn code này sử dụng giá trị iota bắt đầu từ 0.

 

Mẹo iota + 1

const (
    Sleeping = iota + 1
    Walking
    Running
)

func main() {
    var activity Activity
    // activity có giá trị 0, 
    // vì thế nó chưa được khởi tạo

    activity = Sleeping
    // giờ thì activity đã được khởi tạo
}

Sử dụng “iota + 1” để chắc chắn rằng kiểu enum được khởi tạo.

Chạy code trên playground

Đoạn code này sử dụng giá trị iota bắt đầu từ 1

 

Mẫu trạng thái Unknow (Unknown state pattern)

const (
    Unknown = iota
    Sleeping
    Walking
    Running
)

Bắt đầu bằng “Unknow” để chắc chắn enum được khởi tạo.

Chạy code trên playground

Đoạn code này sử dụng giá trị Unknow mặc định.

 

 

Trong bảng chữ cái Hy Lạp, iota là chữ cái thứ chín và là chữ cái nhỏ nhất (về mặt biểu diễn trực quan). Phát âm như thế này. Nó đại diện cho một số lượng rất nhỏ của một cái gì đó.

[[ Trong toán vector, và trong một số ngôn ngữ lập trình, nó được sử dụng để tạo ra một mảng các số nguyên liên tiếp như 1, 2, 3, 4… ]]

♦ Nếu bạn tò mò, bạn có thể đọc thêm về việc implementation các hằng số và iota trong mã nguồn Go. Iota được khai báo ở đâyconst iota = 0, implementation constant ở đây, và hành vi tăng iota là ở đây.

 

•   •   •

  • Iota được nhắc tới trong cuốn sách của Iverson: A Programming Language (APL) năm 1962.
  • Iota được mượn từ APL bởi một trong những người sáng tạo ra Go, Ken Thompson.
  • Một cuộc thảo luận để làm cho iota hoạt động với các biến trong Go 2.

 

Bài viết được dịch từ blog.learngoprogramming.com