First class functions là gì?

Một ngôn ngữ hỗ trợ first class functions cho phép function có thể gán cho biến, truyền vào làm tham số cho function khác và có thể được trả về từ functions khác. Go có hỗ trợ cho first class functions

Trong bài viết này, chúng ta sẽ thảo luận về cú pháp và các trường hợp sử dụng khác nhau của first class functions.

Anonymous functions

Chúng ta bắt đầu với 1 ví dụ đơn giản bằng việc gán function cho 1 biến

package main

import (
    "fmt"
)

func main() {
    a := func() {
        fmt.Println("hello world first class function")
    }
    a()
    fmt.Printf("%T", a)
}

Trong ví dụ trên, chúng ta gán một function cho biến a. Đây là cú pháp để gán một function cho 1 biến. Nếu bạn để ý kỹ, function được gán cho a không có tên. Các functions kiểu này được gọi là anonymous functions bởi vì chúng không có tên

Cách duy nhất để gọi function này là sử dụng biến a

Kết quả

hello world first class function
func()

a() để gọi function và in ra " hello world first class function" và type của a trong trường hợp này là fun()

Chúng ta cũng có thể gọi một anonymous function mà không cần gán nó cho một biến nào cả. Hãy xem cách này được thực hiện như thế nào trong ví dụ sau.

package main

import (
    "fmt"
)

func main() {
    func() {
        fmt.Println("hello world first class function")
    }()
}

Trong ví dụ trên, Sau khi chúng ta định nghĩa một anonymous function được định nghĩa, sau đó sử dụng () ngay sau function để thực hiện gọi function

Kết quả

hello world first class function

Chúng ta cũng có thể truyền đối số cho các anonymous function giống như bất kỳ function nào khác.

package main

import (
    "fmt"
)

func main() {
    func(n string) {
        fmt.Println("Welcome", n)
    }("Gophers")
}

Trong ví dụ trên, một đối số kiểu string được truyền vào hàm ẩn danh ngay sau khi nó được khai báo

Kết quả

Welcome Gophers

Kiểu function do người dùng tự định nghĩa

Giống như cách chúng ta định nghĩa các struct, chúng ta có thể định nghĩa các kiểu function

type add func(a int, b int) int

Đoạn code trên tạo ra một function mới có type add. Func này chấp nhận 2 tham số đầu vào có kiểu int và trả về giá trị cũng là kiểu int. Bây giờ chúng ta có thể định nghĩa các biến type add.

package main

import (
    "fmt"
)

type add func(a int, b int) int

func main() {
    var a add = func(a int, b int) int {
        return a + b
    }
    s := a(5, 6)
    fmt.Println("Sum", s)
}

Trong chương trình trên, chúng ta định nghĩa một biến type add và gán cho nó một function khớp với type add. Sau đó gọi a(5, 6) và gán giá trị cho biến s

Kết quả

Sum 11

Higher-order functions

Theo wiki thì Higher-order functions được định nghĩa là function thực hiện ít nhất một trong những điều sau đây:

  • Nhận 1 hoặc nhiều function khác vào làm tham số
  • Trả về một function khác

Hãy xem các ví dụ cho những trường hợp này

Nhận 1 hoặc nhiều function khác vào làm tham số

package main

import (
    "fmt"
)

func simple(a func(a, b int) int) {
    fmt.Println(a(60, 7))
}

func main() {
    f := func(a, b int) int {
        return a + b
    }
    simple(f)
}

Ở ví dụ trên, chúng ta định nghĩa func simple với tham số là 1 function, nhận vào 2 tham số kiểu int và trả về giá trị kiểu int

Trong func main chúng ta tạo ra anonymous function f. Sau đó gọi simple và truyền f vào làm đối số cho nó

Trả về một function khác

Bây giờ chúng ta hãy viết lại chương trình trên và trả về một function từ function simple.

package main

import (
    "fmt"
)

func simple() func(a, b int) int {
    f := func(a, b int) int {
        return a + b
    }
    return f
}

func main() {
    s := simple()
    fmt.Println(s(60, 7))
}

Trong ví dụ trên, func simple trả về một function nhận hai tham số kiểu int và trả về giá trị kiểu int.

Trong func main chúng ta thực hiện gọi simple và gán giá trị trả về của simple cho biến s. Sau đó gọi biến s và truyền vào 2 đối số là 6 và 7

Closures

Closures là trường hợp đặc biệt của anonymous functions. Closures được hiểu là các anonymous functions truy cập các biến được định nghĩa bên ngoài body của function.

Cùng quan sát ví dụ dưới đây

package main

import (
    "fmt"
)

func main() {
    a := 5
    func() {
        fmt.Println("a =", a)
    }()
}

Trong ví dụ trên, anonymous function truy cập vào biến a có bên ngoài body của nó. Do đó, anonymous function này là một closure.

Mỗi closure đều bị ràng buộc với biến bao quanh nó. Chúng ta có thể tìm hiểu điều này thông qua ví dụ sau

package main

import (
    "fmt"
)

func appendStr() func(string) string {
    t := "Hello"
    c := func(b string) string {
        t = t + " " + b
        return t
    }
    return c
}

func main() {
    a := appendStr()
    b := appendStr()
    fmt.Println(a("World"))
    fmt.Println(b("Everyone"))

    fmt.Println(a("Gopher"))
    fmt.Println(b("!"))
}

Trong chương trình trên, func appendStr trả về một closure. closure này được ràng buộc với biến t.

2 biến ab được khai báo bên trong func main là closure và chúng bị ràng buộc với giá trị của t

Đầu tiên chúng ta gọi func a với đối số là World. Bây giờ giá trị của t trong a trở thành Hello World.

Tiếp theo chúng ta gọi func b với đối số là Everyone, vì b bị ràng buộc với biến t. Nên lúc này giá trị ban đầu của t được khởi tạo lại trong b là Hello

Nhưng trong lần gọi func b tiếp theo thì giá trị ban đầu của t trong b lại là Hello Everyone. Đó là lý do vì sao chúng ta nhận được kết quả sau đây

Kết quả

Hello World
Hello Everyone
Hello World Gopher
Hello Everyone !