Tại sao Go không cho phép khai báo hàm lồng nhau (hàm bên trong hàm)?


87

Chỉnh sửa: Nếu nó không rõ ràng tôi đã hỏi : những vấn đề được giảm thiểu bằng cách không cho phép khai báo hàm lồng nhau là gì?

Lambdas hoạt động như mong đợi:

func main() {
    inc := func(x int) int { return x+1; }
}

Tuy nhiên, khai báo sau bên trong một khai báo không được phép:

func main() {
    func inc(x int) int { return x+1; }
}

Vì lý do gì mà các hàm lồng nhau không được phép?


hmm Tôi không biết bạn có ý định làm điều này hay không func main() { func (x int) int { return x+1; }(3) }
ymg

@YasirG. nhưng đó cũng là lambda, phải không? Tôi không nhận được bình luận của bạn ...
corazza 22/02

chức năng nào sẽ kích hoạt ví dụ thứ hai trong cú pháp cho phép, điều đó không được hỗ trợ bởi trường hợp đầu tiên?
Not_a_Golfer

@yannbane nó là một biểu thức lambda, tôi không nghĩ rằng bạn có thể khai báo một hàm bên trong một hàm khác như JS. Vì vậy, tôi muốn nói rằng phù hợp nhất của bạn là sử dụng lambdas.
ymg

@Not_a_Golfer: Có một khả năng là triển khai nó theo cách JavaScript thực hiện, về cơ bản việc gán một hàm cho một biến rất khác so với việc khai báo một hàm vì luồng điều khiển ảnh hưởng đến các biến đó, trong khi các hàm trong JavaScript không bị ảnh hưởng. Điều đó có nghĩa là bạn có thể gọi inc()trong ví dụ thứ hai trước khi khai báo thực tế. Nhưng! Tôi đang tìm kiếm lý do, tôi không biết nhiều về cờ vây nhưng tôi muốn tìm hiểu logic đằng sau quy tắc này là gì.
corazza

Câu trả lời:


54

Tôi nghĩ có 3 lý do tại sao tính năng rõ ràng này không được phép

  1. Nó sẽ làm phức tạp trình biên dịch một chút. Hiện tại, trình biên dịch biết tất cả các chức năng đều ở cấp cao nhất.
  2. Nó sẽ tạo ra một lớp lỗi lập trình viên mới - bạn có thể cấu trúc lại một cái gì đó và vô tình lồng một số hàm.
  3. Có một cú pháp khác cho các hàm và các bao đóng là một điều tốt. Đóng một hàm có thể tốn kém hơn so với tạo một hàm, vì vậy bạn nên biết mình đang làm.

Tuy nhiên, đó chỉ là ý kiến ​​của tôi - Tôi chưa thấy thông báo chính thức từ các nhà thiết kế ngôn ngữ.


2
Pascal (ít nhất nó là hiện thân của Delphi) đã làm cho chúng đúng và đơn giản: các hàm lồng nhau hoạt động giống như thông thường nhưng cũng có quyền truy cập vào các biến trong phạm vi hàm bao quanh của chúng. Tôi không nghĩ rằng những điều này là khó thực hiện. Mặt khác, đã viết nhiều mã Delphi, tôi không chắc mình không cần đến các hàm lồng nhau: đôi khi chúng cảm thấy tiện lợi nhưng chúng có xu hướng thổi bay hàm bao quanh khiến nó khó đọc được. Ngoài ra, việc truy cập các đối số của cha mẹ chúng có thể làm cho chương trình khó đọc vì các biến này được truy cập ngầm (không được truyền dưới dạng tham số chính thức).
kostix

1
các hàm cục bộ là một bước tái cấu trúc trung gian trên con đường trích xuất các phương thức của bạn. Trong c #, họ đã làm cho những giá trị này trở nên có giá trị hơn khi họ giới thiệu các hàm cục bộ tĩnh không được phép bắt các biến từ hàm bao quanh, vì vậy bạn buộc phải chuyển bất kỳ thứ gì làm tham số. Các nút chức năng cục bộ tĩnh làm cho điểm 3 không phải là vấn đề. Điểm 2 cũng không phải là vấn đề theo quan điểm của tôi.
Cosmin Sontu

47

Chắc chắn rồi. Bạn chỉ cần gán chúng cho một biến:

func main() {
    inc := func(x int) int { return x+1; }
}

4
cần lưu ý, bạn không thể gọi các hàm như vậy (bao gồm) một cách đệ quy.
Mohsin Kale

27

Câu hỏi thường gặp (FAQ)

Tại sao Go không có tính năng X?

Mỗi ngôn ngữ đều chứa các tính năng mới và bỏ qua tính năng yêu thích của ai đó. Go được thiết kế dựa trên tính sai lầm của lập trình, tốc độ biên dịch, tính trực giao của các khái niệm và nhu cầu hỗ trợ các tính năng như đồng thời và thu gom rác. Tính năng yêu thích của bạn có thể bị thiếu vì nó không phù hợp, vì nó ảnh hưởng đến tốc độ biên dịch hoặc sự rõ ràng của thiết kế, hoặc vì nó sẽ làm cho mô hình hệ thống cơ bản trở nên quá khó khăn.

Nếu bạn thấy phiền rằng Go thiếu tính năng X, hãy tha thứ cho chúng tôi và điều tra các tính năng mà Go có. Bạn có thể thấy rằng họ bù đắp bằng những cách thú vị cho việc thiếu X.

Điều gì sẽ biện minh cho sự phức tạp và tốn kém của việc thêm các hàm lồng nhau? Bạn muốn làm gì mà bạn không thể làm được nếu không có các hàm lồng nhau? Vân vân.


19
Công bằng mà nói, tôi không nghĩ bất kỳ ai đã chứng minh bất kỳ sự phức tạp cụ thể nào mà việc cho phép các hàm lồng nhau sẽ gây ra. Ngoài ra, trong khi tôi đồng ý với triết lý được trích dẫn, tôi không chắc là có hợp lý khi đề cập đến các hàm lồng nhau như một "tính năng", cũng giống như việc đề cập đến sự thiếu sót của chúng như một tính năng. Bạn có biết về bất kỳ biến chứng nào mà việc cho phép các hàm lồng nhau sẽ cho phép không? Tôi cho rằng chúng chỉ là đường cú pháp cho lambdas (tôi không thể nghĩ ra bất kỳ hành vi hợp lý nào khác).
joshlf

Vì go được biên dịch nên cách duy nhất để thực hiện AFAIK này, sẽ tạo ra một cú pháp khác để xác định lambdas. Và tôi thực sự không thấy một trường hợp sử dụng nào cho điều đó. bạn không thể có một hàm tĩnh trong một hàm tĩnh được tạo trong thời gian thực - điều gì sẽ xảy ra nếu chúng ta không nhập đường dẫn mã cụ thể xác định hàm?
Not_a_Golfer

Chỉ cần chuyển vào giao diện lambda {} và nhập khẳng định. Ví dụ. f_lambda: = lambda (func () rval {}) hoặc bất kỳ nguyên mẫu nào. Cú pháp khai báo func không hỗ trợ điều này nhưng ngôn ngữ hoàn toàn có.
BadZen


8

Các hàm lồng nhau được phép trong Go. Bạn chỉ cần gán chúng cho các biến cục bộ bên trong hàm bên ngoài và gọi chúng bằng các biến đó.

Thí dụ:

func outerFunction(iterations int, s1, s2 string) int {
    someState := 0
    innerFunction := func(param string) int {
        // Could have another nested function here!
        totalLength := 0
        // Note that the iterations parameter is available
        // in the inner function (closure)
        for i := 0; i < iterations; i++) {
            totalLength += len(param)
        }
        return totalLength
    }
    // Now we can call innerFunction() freely
    someState = innerFunction(s1)
    someState += innerFunction(s2)
    return someState
}
myVar := outerFunction(100, "blah", "meh")

Các chức năng bên trong thường hữu ích cho các tuyến sinh dục cục bộ:

func outerFunction(...) {
    innerFunction := func(...) {
        ...
    }
    go innerFunction(...)
}

Đóng khi di chuyển khác với một số khía cạnh so với chức năng đơn giản. Ví dụ bạn không thể gọi hàm đóng một cách đệ quy.
Michał Zabielski,

7
@ MichałZabielski: Bạn có thể gọi nó một cách đệ quy nếu bạn khai báo nó trước khi bạn định nghĩa nó.
P Daddy,

1

Bạn chỉ cần gọi nó ngay lập tức bằng cách thêm ()vào cuối.

func main() {
    func inc(x int) int { return x+1; }()
}

Chỉnh sửa: không thể có tên hàm ... vì vậy nó chỉ là một hàm lambda được gọi ngay lập tức:

func main() {
    func(x int) int { return x+1; }()
}

1
Uhh rằng không phù hợp với những gì người ta mong chờ của một định nghĩa hàm
Corazza

1
@corazza À, lúc đọc câu hỏi mình đã bỏ sót từ "khai báo" rồi. Lỗi của tôi.
Nick

1
@corazza Ngoài ra, tôi cũng sai cú pháp. Cần thiết để loại bỏ tên chức năng. Vì vậy, về cơ bản nó là một hàm lambda được gọi ngay lập tức.
Nick
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.