Đây là phần cuối cùng trong series bài viết Go cơ bản, giúp các bạn tiếp cận với Go một cách basic nhất có thể dành cho các bạn từ ngôn ngữ khác qua muốn adapt nhanh, hoặc các bạn mới học lập trình.
- Phần 1: Cơ bản về kiểu dữ liệu và toàn tử
- Phần 2: Các cấu trúc điều khiển
Sử dụng function giúp chúng ta sắp xếp code gọn gàng hơn cũn như dễ đọc hơn, và có thể dùng đi dùng lại một logic nhiều lần.
Trong Go, function được xem như bất kỳ kiểu dữ liệu nào khác đấy. Chúng có thể được truyền như là tham số cho các function khác, được trả về từ các function, và được lưu trữ trong các biến.
Sử dụng Function
Trước tiên, chúng ta sẽ tới với một ví dụ về function đơn giản, nhận vào một string và trả về một string:
func greet(name string) string { return "Hello, " + name
}
Và cách chúng ta gọi function, cũng tương tự như các trường hợp khác
greeting := greet("Phuong")
// Hello, Phuong
Tuy nhiên, điểm khác biệt nằm ở chỗ Go còn hỗ trợ function trả về một lúc nhiều giá trị, điều này rất tiện lợi vì đôi khi chỉ một giá trị trả về không đủ. Chúng ta còn có thể đặt tên cho các giá trị trả về, giúp code dễ đọc (readable) hơn:
func swap(x, y int) (a, b int) { a, b = y, x return // không cần "return a, b"
}
Đây cũng chính là cách chúng ta handle error trong Go, một function thường sẽ trả về (res, error) nhằm handle errors một cách tường minh, không sử dụng try catch (ít nhất là ở hiện tại Go 1.20).
Variadic Function
Một function còn có thể nhận không giới hạn các tham số, những function đặc biệt này được gọi là variadic function, và chúng cực kỳ hữu ích khi bạn không biết sẽ cần truyền bao nhiêu tham số cho một function.
Để định nghĩa một function variadic trong Go, bạn sử dụng cú pháp ... trước kiểu của tham số cuối cùng. Ví dụ:
func sum(numbers ...int) int { total := 0 for _, num := range numbers { total += num } return total
}
Function này gọi là sum, nhận một số lượng chưa biết giá trị kiểu int và trả về tổng của tất cả các số đó. Bạn có thể gọi nó như sau:
sum := sum(1, 2, 3, 4, 5) // 15
"Mình có một slice/ array kiểu int thì truyền vào như thế nào?"
Syntax ...
không chỉ sử dụng cho variadic function mà còn có thể dùng để "extract" một array hay slice thành nhiều giá trị, như thế này:
nums := []int{1, 2, 3, 4, 5}
sum := sum(nums...) // 15
Closure Function
Closure function là cách mà chúng ta tạo ra một function có trạng thái (stateful function), bạn có thể tưởng tượng như một struct chứa biến và hàm vậy.
Những function này được tạo bằng cách định nghĩa một function trong một function khác, và chúng có quyền truy cập vào tất cả các biến trong phạm vi bên ngoài của chúng.
func outer() func() int { x := 0 // <--- x là trạng thái của function được tạo bởi outer() return func() int { x++ return x }
}
Function ở trên tên là outer, định nghĩa và trả về một function khác nhằm tăng và trả về biến x được định nghĩa trong phạm vi bên ngoài. Bạn có thể sử dụng function bên trong này như sau:
increment := outer() // tạo tên gọi function là increment increment() // 1
increment() // 2
increment() // 3
Mỗi lần bạn gọi function increment trong ví dụ, nó sẽ tăng giá trị của biến x lên 1 và trả về giá trị mới của x mặc dù chúng ta gọi đúng hàm đó.
Một số nhược điểm về function Go
Theo ý kiến của mình, function Go có một số nhược điểm và tính năng bị thiếu có thể gây khó chịu. Dưới đây là một số tính năng bị thiếu trong Go (phiên bản Go 1.20):
- Default argument values: Go không có cách xây dựng để chỉ định giá trị mặc định cho tham số function.
- Function overloading: Go không có function overloading, tức là khả năng định nghĩa nhiều function với cùng tên nhưng có danh sách tham số khác nhau.
- Named arguments: Go không có cách xây dựng để chỉ định tên của tham số khi gọi một function. Điều này có nghĩa là bạn phải truyền các tham số theo đúng thứ tự.
Claims
Bài viết được dịch từ Go Fundamentals: Functions and Missing Features (P3)