2012-06-10 41 views
8

Chúng ta hãy nói rằng chúng ta muốn thực hiện sau tính toán:chức năng lồng nhau gọi trong GO

outval/err = f3(f3(f1(inval))

nơi mỗi f1, f2, f3 có thể thất bại với một lỗi tại thời điểm đó chúng tôi dừng việc tính toán và thiết lập err lỗi được trả về bởi hàm lỗi. (Tất nhiên, làm tổ có thể tùy tiện lâu)

Trong các ngôn ngữ như C++/Java/C# nó có thể dễ dàng thực hiện bằng cách f1, f2f3 ném một ngoại lệ và kèm theo các tính toán trong một khối try-catch, trong khi ở các ngôn ngữ như Haskell chúng ta có thể sử dụng monads để thay thế.

Bây giờ tôi đang cố gắng để thực hiện nó trong GO và cách tiếp cận duy nhất tôi có thể nghĩ đến là rõ ràng nếu-khác thang đó là khá tiết. Tôi không có vấn đề nếu chúng ta không thể tổ chức các cuộc gọi, nhưng theo ý kiến ​​của tôi thêm kiểm tra lỗi sau mỗi dòng trong mã trông xấu xí và nó phá vỡ dòng chảy. Tôi muốn biết nếu có cách nào tốt hơn để làm điều đó.

Edit: Chỉnh sửa theo nhận xét của peterSO
Dưới đây là ví dụ cụ thể và thực hiện đơn giản

package main 

import "fmt" 

func f1(in int) (out int, err error) { 
    return in + 1, err 
} 

func f2(in int) (out int, err error) { 
    return in + 2, err 
} 

func f3(in int) (out int, err error) { 
    return in + 3, err 
} 

func calc(in int) (out int, err error) { 
    var temp1, temp2 int 
    temp1, err = f1(in) 
    if err != nil { 
     return temp1, err 
    } 
    temp2, err = f2(temp1) 
    if err != nil { 
     return temp2, err 
    } 
    return f3(temp2) 
} 

func main() { 
    inval := 0 
    outval, err := calc3(inval) 
    fmt.Println(inval, outval, err) 
} 

Những gì tôi đang cố gắng để minh họa là, chức năng calc hiện một số tính toán có thể với sự giúp đỡ của các chức năng thư viện mà có thể thất bại và ngữ nghĩa là nếu bất kỳ cuộc gọi không thành công calc tuyên truyền lỗi cho người gọi (tương tự như không xử lý ngoại lệ). Theo tôi, mã cho calc là xấu xí.

giữa cho trường hợp cụ thể này, nơi tất cả các chức năng thư viện có chính xác cùng một chữ ký, chúng ta có thể làm cho đoạn code tốt hơn (Tôi đang sử dụng ý tưởng từ http://golang.org/doc/articles/wiki/#tmp_269)

func saferun(f func (int) (int, error)) func (int, error) (int, error) { 
    return func (in int, err error) (int, error) { 
     if err != nil { 
      return in, err 
     } 
     return f(in) 
    } 
} 

Sau đó, chúng ta có thể xác định lại calc như

func calc(in int) (out int, err error) { 
    return saferun(f3)(saferun(f2)(f1(in))) 
} 

hoặc dưới dạng

func calc(in int) (out int, err error) { 
    sf2 := saferun(f2) 
    sf3 := saferun(f3) 
    return sf3(sf2(f1(in))) 
} 

Nhưng không có generics s upport, tôi không chắc chắn làm thế nào tôi có thể sử dụng phương pháp này cho bất kỳ tập hợp các chức năng thư viện.

Trả lời

5

Cuộc thảo luận giữa lỗi so với ngoại lệ là một sự kiện dài và tẻ nhạt. Do đó tôi sẽ không đi vào nó.

Câu trả lời đơn giản nhất cho câu hỏi của bạn liên quan đến các hàm defer, panic và tích hợp recover của Go được thảo luận trong bài đăng this blog. Họ có thể cung cấp hành vi tương tự như trường hợp ngoại lệ.

package main 

import "fmt" 

func main() { 
    defer func() { 
     // This recovers from a panic if one occurred. 
     if x := recover(); x != nil { 
      fmt.Printf("%v\n", x) 
     } 
    }() 

    value := f(f(f(1))) 
    fmt.Printf("%d\n", value) 
} 

func f(i int) int { 
    value := i*i + 1 

    // something goes wrong, panic instead of returning an error. 
    panic("ohnoes") 

    return value 
} 
+0

Cảm ơn bạn đã trả lời nhưng tôi không chắc rằng tôi hiểu cách tiếp cận của bạn. Điều gì xảy ra nếu tôi không kiểm soát chữ ký của f1, f2, f3, v.v. vì chúng là các hàm thư viện.Trong quan điểm khiêm tốn của tôi xử lý cơ sở hạ tầng trong các ngôn ngữ khác không yêu cầu bạn phải có một kiểm soát – Suyog

+2

Suyog, trích dẫn từ câu hỏi ban đầu của bạn, "... thực hiện bằng cách có f1, f2 và f3 ném một ngoại lệ ...." có vẻ như bạn cho phép thay đổi f1, f2 và f3 để ném ngoại lệ. Làm họ hoảng loạn không khác gì. Panic là cơ chế có sẵn trong Go để thư giãn stack từ độ sâu tùy ý, trả về một giá trị trong tiến trình. Nó không phải là cách thành ngữ và ưu tiên để xử lý lỗi trong Go, nhưng nó là cơ chế sẽ làm những gì bạn yêu cầu. – Sonia

+0

Tôi có nghĩa là f1, f2, f3 đã được xác định để ném một ngoại lệ. Xin lỗi vì từ ngữ xấu. Trong ngôn ngữ như JAVA, nhiều chức năng thư viện được định nghĩa để ném một ngoại lệ, trong khi trong GO có vẻ như mẫu là có chức năng thư viện trả về lỗi. Vì vậy, vấn đề tôi đang phải đối mặt là tôi phải kiểm tra lỗi ngay lập tức đó là kết quả bằng văn bản lặp đi lặp lại mã. – Suyog

0

Không có ví dụ cụ thể, bạn đang nghiêng cối xay gió. Ví dụ, theo định nghĩa của bạn, hàm fn trả về một giá trị và bất kỳ lỗi nào. Các chức năng fn là các hàm gói có chữ ký không thể thay đổi được. Sử dụng ví dụ của bạn,

package main 

import "fmt" 

func f1(in int) (out int, err error) { 
    return in + 1, err 
} 

func f2(in int) (out int, err error) { 
    return in + 2, err 
} 

func f3(in int) (out int, err error) { 
    return in + 3, err 
} 

func main() { 
    inval := 0 
    outval, err := f3(f2(f1(inval))) 
    fmt.Println(inval, outval, err) 
} 

Bạn sẽ lấy ví dụ để biên dịch và chạy như thế nào?

+0

Cảm ơn bạn đã trả lời, tôi cập nhật câu hỏi theo đề xuất – Suyog

7

Nếu bạn thực sự muốn có thể thực hiện việc này, bạn có thể sử dụng chức năng soạn thư.

func compose(fs ...func(Value) (OutVal, error)) func(Value) (OutVal, error) { 
    return func(val Value) OutVal, Error { 
    sVal := val 
    var err error 
    for _, f := range fs { 
     sval, err = f(val) 
     if err != nil { 
     // bail here and return the val 
     return nil, err 
     } 
    } 
    return sval, nil 
    } 
} 

outVal, err := compose(f1, f2)(inVal) 

Hầu hết thời gian bạn có thể muốn đơn giản hơn điều này vì khó có thể hiểu mã của bạn khi họ gặp phải.

+0

Điều đó hữu ích, cảm ơn! – Suyog

7

Đầu tiên, phiên bản mở rộng của kiểu try-catch mà bạn đã quen, vay mượn rõ ràng từ câu trả lời của jimt và câu trả lời của PeterSO.

package main 

import "fmt" 

// Some dummy library functions with different signatures. 
// Per idiomatic Go, they return error values if they have a problem. 
func f1(in string) (out int, err error) { 
    return len(in), err 
} 

func f2(in int) (out int, err error) { 
    return in + 1, err 
} 

func f3(in int) (out float64, err error) { 
    return float64(in) + .5, err 
} 

func main() { 
    inval := "one" 

    // calc3 three is the function you want to call that does a computation 
    // involving f1, f2, and f3 and returns any error that crops up. 
    outval, err := calc3(inval) 

    fmt.Println("inval: ", inval) 
    fmt.Println("outval:", outval) 
    fmt.Println("err: ", err) 
} 

func calc3(in string) (out float64, err error) { 
    // Ignore the following big comment and the deferred function for a moment, 
    // skip to the comment on the return statement, the last line of calc3... 
    defer func() { 
     // After viewing what the fXp function do, this function can make 
     // sense. As a deferred function it runs whenever calc3 returns-- 
     // whether a panic has happened or not. 
     // 
     // It first calls recover. If no panic has happened, recover returns 
     // nil and calc3 is allowed to return normally. 
     // 
     // Otherwise it does a type assertion (the value.(type) syntax) 
     // to make sure that x is of type error and to get the actual error 
     // value. 
     // 
     // It does a tricky thing then. The deferred function, being a 
     // function literal, is a closure. Specifically, it has access to 
     // calc3's return value "err" and can force calc3 to return an error. 
     // A line simply saying "err = xErr" would be enough, but we can 
     // do better by annotating the error (something specific from f1, 
     // f2, or f3) with the context in which it occurred (calc3). 
     // It allows calc3 to return then, with this descriptive error. 
     // 
     // If x is somehow non-nil and yet not an error value that we are 
     // expecting, we re-panic with this value, effectively passing it on 
     // to allow a higer level function to catch it. 
     if x := recover(); x != nil { 
      if xErr, ok := x.(error); ok { 
       err = fmt.Errorf("calc3 error: %v", xErr) 
       return 
      } 
      panic(x) 
     } 
    }() 
    // ... this is the way you want to write your code, without "breaking 
    // the flow." 
    return f3p(f2p(f1p(in))), nil 
} 

// So, notice that we wrote the computation in calc3 not with the original 
// fX functions, but with fXp functions. These are wrappers that catch 
// any error and panic, removing the error from the function signature. 
// Yes, you must write a wrapper for each library function you want to call. 
// It's pretty easy though: 
func f1p(in string) int { 
    v, err := f1(in) 
    if err != nil { 
     panic(err) 
    } 
    return v 
} 

func f2p(in int) int { 
    v, err := f2(in) 
    if err != nil { 
     panic(err) 
    } 
    return v 
} 

func f3p(in int) float64 { 
    v, err := f3(in) 
    if err != nil { 
     panic(err) 
    } 
    return v 
} 
// Now that you've seen the wrappers that panic rather than returning errors, 
// go back and look at the big comment in the deferred function in calc3. 

Vì vậy, bạn có thể phản đối rằng bạn đã yêu cầu dễ dàng hơn và điều này thì không. Không có đối số trên toàn bộ, nhưng nếu thư viện hoạt động tất cả các giá trị lỗi trả về và bạn muốn gọi hàm chuỗi không có giá trị lỗi, giải pháp có sẵn là bọc các hàm thư viện và trình bao bọc rất mỏng và dễ viết. Phần khó nhất khác là hàm bị trì hoãn, nhưng đó là một mẫu mà bạn có thể học và sử dụng lại và nó chỉ là một vài dòng mã.

Tôi không muốn quảng bá giải pháp này quá nhiều vì nó không phải là giải pháp thường được sử dụng. Đó là một mẫu hợp lệ mặc dù, và có một số trường hợp sử dụng mà nó là thích hợp.

Xử lý lỗi là một chủ đề lớn, như đã đề cập. "Cách xử lý lỗi tốt trong Go là gì?" sẽ là một câu hỏi hay đối với SO ngoại trừ vấn đề là nó không đạt được "toàn bộ cuốn sách" critereon. Tôi có thể tưởng tượng toàn bộ cuốn sách về chủ đề xử lý lỗi trong Go. Thay vào đó, tôi sẽ cung cấp quan sát chung của tôi rằng nếu bạn chỉ bắt đầu làm việc với các giá trị lỗi thay vì cố gắng làm cho chúng biến mất, sau một thời gian bạn bắt đầu hiểu được lợi thế của việc này. Trông giống như một bậc thang dài dòng của câu lệnh if trong ví dụ đồ chơi như chúng ta đã sử dụng ở đây có thể vẫn trông giống như một bậc thang dài dòng của câu lệnh if khi bạn viết nó lần đầu tiên trong một chương trình thế giới thực. Khi bạn thực sự cần phải xử lý những lỗi đó, bạn quay trở lại mã và đột nhiên thấy nó như là tất cả các lệnh đang chờ bạn để xác thịt với mã xử lý lỗi thực sự. Bạn có thể thấy chỉ cần làm gì vì mã gây ra lỗi là ngay tại đó. Bạn có thể làm cho người dùng thấy một thông báo lỗi cấp thấp không rõ ràng và thay vào đó hiển thị một cái gì đó có ý nghĩa. Bạn là lập trình viên được nhắc làm điều đúng thay vì chấp nhận một điều mặc định.

Để có câu trả lời toàn diện hơn, một nguồn lực tốt để bắt đầu là bài viết Error Handling and Go. Nếu bạn tìm kiếm thông qua các Go-Nuts messages có những cuộc thảo luận dài về vấn đề đó là tốt. Các hàm trong thư viện chuẩn gọi nhau khá nhiều, (ngạc nhiên) và do đó mã nguồn của thư viện chuẩn chứa nhiều ví dụ về lỗi xử lý. Đây là những ví dụ điển hình để nghiên cứu vì mã được viết bởi tác giả Go đang quảng bá phong cách lập trình này làm việc với các giá trị lỗi.

+0

Cảm ơn rất nhiều vì đã trả lời sâu rộng! – Suyog

0

Tìm thấy thư gửi thread trên go-nut cho chủ đề này. Thêm nó để tham khảo.

0

Quá xấu này đã bị đóng đã ... này:

value := f(f(f(1))) 

không phải là một ví dụ về chaining nhưng làm tổ. Chuỗi phải giống như sau:

c.funA().funB().funC() 

Đây là một hoạt động example.

+0

Điểm công bằng; đây không phải là câu trả lời mà là bình luận – leonbloy

Các vấn đề liên quan