英文:
Go method chaining and error handling
问题
我想在Go语言中创建一个方法链式调用的API。在我找到的所有示例中,链式操作似乎总是成功的,而我无法保证这一点。因此,我尝试扩展这些操作以添加错误返回值。
如果我这样做:
package main
import "fmt"
type Chain struct {
}
func (v *Chain) funA() (*Chain, error) {
fmt.Println("A")
return v, nil
}
func (v *Chain) funB() (*Chain, error) {
fmt.Println("B")
return v, nil
}
func (v *Chain) funC() (*Chain, error) {
fmt.Println("C")
return v, nil
}
func main() {
fmt.Println("Hello, playground")
c := Chain{}
d, err := c.funA().funB().funC() // line 24
}
编译器告诉我chain-err-test.go:24: multiple-value c.funA() in single-value context
,并且无法编译。有没有一种好的方法让funcA
、funcB
和funcC
报告错误并停止链式调用呢?
英文:
I want to create a method chaining API in Go. In all examples I can find the chained operations seem always to succeed which I can't guarantee. I therefore try to extend these to add the error return value.
If I do it like this
package main
import "fmt"
type Chain struct {
}
func (v *Chain)funA() (*Chain, error ) {
fmt.Println("A")
return v, nil
}
func (v *Chain)funB() (*Chain, error) {
fmt.Println("B")
return v, nil
}
func (v *Chain)funC() (*Chain, error) {
fmt.Println("C")
return v, nil
}
func main() {
fmt.Println("Hello, playground")
c := Chain{}
d, err := c.funA().funB().funC() // line 24
}
The compiler tells me chain-err-test.go:24: multiple-value c.funA() in single-value context
and won't compile. Is there a good way so funcA, funcB and funcC can report an error and stop that chain?
答案1
得分: 43
很抱歉,对于你的问题,目前没有一个好的解决方案。解决方法相当复杂(例如添加错误通道等),成本超过了收益。
在Go语言中,方法链不是一种惯用法(至少对于可能出错的方法而言)。这并不是因为方法链有什么特别的问题,而是因为在Go语言中,返回错误而不是引发异常是一种惯用法。其他答案都是一些变通方法,但都不是惯用的做法。
关于你的问题,是因为Go语言支持多返回值吗?实际上不是。Python支持多返回值,Java也可以通过Tuple<T1, T2>
类实现多返回值;在这两种语言中,方法链是常见的。之所以可以这样做,是因为它们习惯上通过异常来传递错误信息。异常会立即停止方法链,并跳转到相关的异常处理程序。Go语言的开发者选择返回错误而不是引发异常,就是为了避免这种行为。
英文:
> Is there a good way so funcA, funcB and funcC can report an error and stop that chain?
Unfortunately, no, there is no good solution to your problem. Workarounds are sufficiently complex (adding in error channels, etc) that the cost exceeds the gain.
Method chaining isn't an idiom in Go (at least not for methods that can possibly error). This isn't because there is anything particularly wrong with method chains, but a consequence of the idiom of returning errors instead of panicking. The other answers are workarounds, but none are idiomatic.
> Can I ask, is it not idiomatic to chain methods in Go because of the consequence of returning error as we do in Go, or is it more generally a consequence of having multiple method returns?
Good question, but it's not because Go supports multiple returns. Python supports multiple returns, and Java can too via a Tuple<T1, T2>
class; method chains are common in both languages. The reason these languages can get away with it is because they idiomatically communicate errors via exceptions. Exceptions stop the method chain immediately and jump to the relevant exception handler. This is the behavior the Go developers were specifically trying to avoid by choosing to return errors instead.
答案2
得分: 11
你可以尝试这样做:
package main
import (
"errors"
"fmt"
)
type Chain struct {
err error
}
func (v *Chain) funA() *Chain {
if v.err != nil {
return v
}
fmt.Println("A")
return v
}
func (v *Chain) funB() *Chain {
if v.err != nil {
return v
}
v.err = errors.New("error at funB")
fmt.Println("B")
return v
}
func (v *Chain) funC() *Chain {
if v.err != nil {
return v
}
fmt.Println("C")
return v
}
func main() {
c := Chain{}
d := c.funA().funB().funC()
fmt.Println(d.err)
}
你可以在这里查看代码:https://play.golang.org/p/dVn_DGWt1p_H
英文:
You can try like that:
https://play.golang.org/p/dVn_DGWt1p_H
package main
import (
"errors"
"fmt"
)
type Chain struct {
err error
}
func (v *Chain) funA() *Chain {
if v.err != nil {
return v
}
fmt.Println("A")
return v
}
func (v *Chain) funB() *Chain {
if v.err != nil {
return v
}
v.err = errors.New("error at funB")
fmt.Println("B")
return v
}
func (v *Chain) funC() *Chain {
if v.err != nil {
return v
}
fmt.Println("C")
return v
}
func main() {
c := Chain{}
d := c.funA().funB().funC()
fmt.Println(d.err)
}
答案3
得分: 3
如果你对代码有控制权,并且函数签名是相同的,你可以编写类似以下的代码:
func ChainCall(fns ...func() (*Chain, error)) (err error) {
for _, fn := range fns {
if _, err = fn(); err != nil {
break
}
}
return
}
<kbd>playground</kbd>
英文:
If you have control over the code and the function signature is identical you can write something like:
func ChainCall(fns ...func() (*Chain, error)) (err error) {
for _, fn := range fns {
if _, err = fn(); err != nil {
break
}
}
return
}
<kbd>playground</kbd>
答案4
得分: 1
你可以通过收集一系列函数来使你的链式调用变得惰性。
package main
import (
"fmt"
)
type (
chainFunc func() error
funcsChain struct {
funcs []chainFunc
}
)
func Chain() funcsChain {
return funcsChain{}
}
func (chain funcsChain) Say(s string) funcsChain {
f := func() error {
fmt.Println(s)
return nil
}
return funcsChain{append(chain.funcs, f)}
}
func (chain funcsChain) TryToSay(s string) funcsChain {
f := func() error {
return fmt.Errorf("don't speek golish")
}
return funcsChain{append(chain.funcs, f)}
}
func (chain funcsChain) Execute() (i int, err error) {
for i, f := range chain.funcs {
if err := f(); err != nil {
return i, err
}
}
return -1, nil
}
func main() {
i, err := Chain().
Say("Hello, playground").
TryToSay("go cannot into chains").
Execute()
fmt.Printf("i: %d, err: %s", i, err)
}
希望对你有帮助!
英文:
You can make your chain lazy by collecting a slice of funtions
package main
import (
"fmt"
)
type (
chainFunc func() error
funcsChain struct {
funcs []chainFunc
}
)
func Chain() funcsChain {
return funcsChain{}
}
func (chain funcsChain) Say(s string) funcsChain {
f := func() error {
fmt.Println(s)
return nil
}
return funcsChain{append(chain.funcs, f)}
}
func (chain funcsChain) TryToSay(s string) funcsChain {
f := func() error {
return fmt.Errorf("don't speek golish")
}
return funcsChain{append(chain.funcs, f)}
}
func (chain funcsChain) Execute() (i int, err error) {
for i, f := range chain.funcs {
if err := f(); err != nil {
return i, err
}
}
return -1, nil
}
func main() {
i, err := Chain().
Say("Hello, playground").
TryToSay("go cannot into chains").
Execute()
fmt.Printf("i: %d, err: %s", i, err)
}
答案5
得分: 0
你实际上不需要通道和/或上下文来使类似这样的代码工作。我认为这个实现满足了你的所有要求,但不用说,这会让人感到不舒服。Go语言不是一种函数式语言,最好不要将其视为函数式语言。
package main
import (
"errors"
"fmt"
"strconv"
)
type Res[T any] struct {
Val T
Halt bool
Err error
}
// 执行参数,直到检测到停止信号
func (r *Res[T]) Chain(args ...func() *Res[T]) *Res[T] {
temp := r
for _, f := range args {
if temp = f(); temp.Halt {
break
}
}
return temp
}
// 示例函数,将任意类型转换为字符串,然后转换为整数,再转换为字符串
func (r *Res[T]) funA() *Res[string] {
s := fmt.Sprint(r.Val)
i, err := strconv.Atoi(s)
if err != nil {
r.Err = fmt.Errorf("包装错误:%w", err)
}
fmt.Println("管道中的函数被强制使用 Res[string]")
return &Res[string]{Val: strconv.Itoa(i), Err: r.Err}
}
func (r *Res[T]) funB() *Res[T] {
prev := errors.Unwrap(r.Err)
fmt.Printf("解包错误:%v\n", prev)
// 如果有错误发生,则发出停止信号
if prev != nil {
r.Halt = true
}
return r
}
func (r *Res[T]) funC() *Res[T] {
fmt.Println("这个函数永远不会被执行...")
return r
}
func (r *Res[T]) funD() *Res[T] {
fmt.Println("...但这个函数会被执行")
return r
}
func funE() *Res[string] {
fmt.Println("Chain甚至可以接受非方法,但要注意返回值为nil")
return nil
}
func main() {
r := Res[string]{}
r.Chain(r.funA, r.funB, r.funC).funD().Chain(funE).funC() // ...等等
}
希望这对你有帮助!
英文:
You don't actually need channels and/or contexts to get something like this to work. I think this implementation meets all your requirements but needless to say, this leaves a sour taste. Go is not a functional language and it's best not to treat it as such.
package main
import (
"errors"
"fmt"
"strconv"
)
type Res[T any] struct {
Val T
Halt bool
Err error
}
// executes arguments until a halting signal is detected
func (r *Res[T]) Chain(args ...func() *Res[T]) *Res[T] {
temp := r
for _, f := range args {
if temp = f(); temp.Halt {
break
}
}
return temp
}
// example function, converts any type -> string -> int -> string
func (r *Res[T]) funA() *Res[string] {
s := fmt.Sprint(r.Val)
i, err := strconv.Atoi(s)
if err != nil {
r.Err = fmt.Errorf("wrapping error: %w", err)
}
fmt.Println("the function down the pipe is forced to work with Res[string]")
return &Res[string]{Val: strconv.Itoa(i), Err: r.Err}
}
func (r *Res[T]) funB() *Res[T] {
prev := errors.Unwrap(r.Err)
fmt.Printf("unwrapped error: %v\n", prev)
// signal a halt if something is wrong
if prev != nil {
r.Halt = true
}
return r
}
func (r *Res[T]) funC() *Res[T] {
fmt.Println("this one never gets executed...")
return r
}
func (r *Res[T]) funD() *Res[T] {
fmt.Println("...but this one does")
return r
}
func funE() *Res[string] {
fmt.Println("Chain can even take non-methods, but beware of nil returns")
return nil
}
func main() {
r := Res[string]{}
r.Chain(r.funA, r.funB, r.funC).funD().Chain(funE).funC() // ... and so on
}
答案6
得分: -1
如何这样处理:创建一个委托Chain
和error
的结构体,并返回它而不是两个值。例如:
package main
import "fmt"
type Chain struct {
}
type ChainAndError struct {
*Chain
error
}
func (v *Chain) funA() ChainAndError {
fmt.Println("A")
return ChainAndError{v, nil}
}
func (v *Chain) funB() ChainAndError {
fmt.Println("B")
return ChainAndError{v, nil}
}
func (v *Chain) funC() ChainAndError {
fmt.Println("C")
return ChainAndError{v, nil}
}
func main() {
fmt.Println("Hello, playground")
c := Chain{}
result := c.funA().funB().funC() // line 24
fmt.Println(result.error)
}
这样,你可以通过调用funA().funB().funC()
来依次执行funA
,funB
和funC
方法,并在最后打印出result.error
的值。
英文:
How about this approach: Create a struct that delegates Chain
and error
, and return it instead of two values. e.g.:
package main
import "fmt"
type Chain struct {
}
type ChainAndError struct {
*Chain
error
}
func (v *Chain)funA() ChainAndError {
fmt.Println("A")
return ChainAndError{v, nil}
}
func (v *Chain)funB() ChainAndError {
fmt.Println("B")
return ChainAndError{v, nil}
}
func (v *Chain)funC() ChainAndError {
fmt.Println("C")
return ChainAndError{v, nil}
}
func main() {
fmt.Println("Hello, playground")
c := Chain{}
result := c.funA().funB().funC() // line 24
fmt.Println(result.error)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论