DEV Community

Pallat Anchaleechamaikorn
Pallat Anchaleechamaikorn

Posted on

3

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 นั่นแหล่ะ จบ

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more