DEV Community

Pallat Anchaleechamaikorn
Pallat Anchaleechamaikorn

Posted on

range-over-func in Go

#go

Go1.23 ได้นำฟีเจอร์ range-over-func ที่เป็น experiment ใน go1.22 มาให้ใช้จริง ซึ่งถ้าใครได้อ่านโค้ดตัวอย่างเข้าไปแล้วอาจจะต้องนั่งสมาธิกันนานหน่อย อย่ากระนั้นเลย พี่ยอดจะมาอธิบายแบบง่ายสุดๆให้อ่านกัน

เริ่มจาก spec ของ For statements with range clause ได้เพิ่มเติม Expression เข้ามา 3 แบบคือ

func(func() bool)
func(func(V) bool)
func(func(K, V) bool)
Enter fullscreen mode Exit fullscreen mode

โดยพี่จะขอเพิ่มตัวแปรให้ตัวนึงเพิ่อจะได้อธิบายดังนี้

f func(yield func() bool)
f func(yield func(V) bool)
f func(yield func(K, V) bool)
Enter fullscreen mode Exit fullscreen mode

ใน spec บอกว่า เมื่อเราใช้ฟังก์ชั่น f ไปเป็น expression ใน range ทุกครั้งที่เราเรียกฟังก์ชั่น yield ในนั้นก่อนจะจบฟังก์ชั่น f เราจะได้ผลลัพธ์ในแต่ละลูป เท่ากับค่าที่เราใส่เข้าไปใน yield อธิบายแล้วก็ยังงง เขียนโค้ดเลยดีกว่า

func main() {
    for range loop {
        fmt.Println("-")
    }
}

func loop(yield func() bool) {
    yield()
    yield()
}
Enter fullscreen mode Exit fullscreen mode

output:

-
-
Enter fullscreen mode Exit fullscreen mode

ถ้าเราเขียนโค้ดแบบนี้ เราจะได้ loop 2 รอบถ้วน เพราะเราเรียก yield 2 ครั้งใน f ตาม spec ซึ่งในที่นี่เราตั้งชื่อว่า loop และมันจะไม่คืนค่าอะไรออกมาให้เรา เพราะเราเลือกใช้ pattern ที่ yield ไม่รับ arguments ใดๆเลย

อีกตัวอย่าง

func main() {
    for i := range loop {
        fmt.Println(i)
    }
}

func loop(yield func(int) bool) {
    yield(3)
    yield(7)
}
Enter fullscreen mode Exit fullscreen mode

output:

3
7
Enter fullscreen mode Exit fullscreen mode

แบบนี้เราจะได้ 2 รอบเหมือนกัน เพราะเราเรียก yield ครั้ง และทีนี้ range จะคืนค่ามาให้ 2 ค่า ซึ่งก็คือ 3 และ 7 ที่เราใช้เรียก yield ในแต่ละครั้ง

อีกตัวอย่าง

func main() {
    for k, v := range loop {
        fmt.Println(k, v)
    }
}

func loop(yield func(int, string) bool) {
    yield(3, "three")
    yield(5, "five")
    yield(7, "seven")
}
Enter fullscreen mode Exit fullscreen mode

output:

3 three
5 five
7 seven
Enter fullscreen mode Exit fullscreen mode

เราก็จะได้ loop 3 รอบ และได้ค่าออกมารอบละ 2 ค่าตามที่เราใส่ให้ yield ในแต่ละครั้ง
และเรายังสามารถเรียก yield ด้วยการใส่ arguments แบบไหนก็ได้เช่น

func loop(yield func(string, bool) bool) {
    yield("three", true)
    yield("five", false)
    yield("seven", false)
}
Enter fullscreen mode Exit fullscreen mode

ทีนี้พอเราเข้าใจกลไกลของมันแล้ว เวลาเราไปอ่านตัวอย่างยากๆเราจะเข้าใจมากขึ้นเช่นตัวอย่างใน Go Wiki: Rangefunc Experiment

package slices

func Backward[E any](s []E) func(func(int, E) bool) {
    return func(yield func(int, E) bool) {
        for i := len(s)-1; i >= 0; i-- {
            if !yield(i, s[i]) {
                return
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

main

s := []string{"hello", "world"}
for i, x := range slices.Backward(s) {
    fmt.Println(i, x)
}
Enter fullscreen mode Exit fullscreen mode

อ่านง่ายขึ้นเลยใช่ไหม สุดท้ายจะเอาไปประยุกต์ยังไงก็แล้วแต่ เราดูแค่ตอนที่เรียก yield ว่าเรียกกี่รอบ ก็จะได้เท่านั้นรอบตอนเอาไปใส่ใน range
ส่วนค่าที่จะได้ออกมาก็คือค่าที่หย่อนลงไปใน yield นั่นแหล่ะ จบ

Top comments (0)