Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/google-cloud-platform/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在GO中嵌套函数调用_Go - Fatal编程技术网

在GO中嵌套函数调用

在GO中嵌套函数调用,go,Go,假设我们要实现以下计算: outval/err=f3(f3(f1(无效)) 当f1、f2、f3中的每一个都可能失败并出现错误时,我们停止计算并将err设置为失败函数返回的错误。(当然,嵌套可以任意长) 在像C++/JAVA/C这样的语言中,可以通过让f1、f2和f3抛出异常并将计算包含在try-catch块中来轻松完成,而在像Haskell这样的语言中,我们可以使用单子 现在我正试图在GO中实现它,我能想到的唯一方法是if else ladder,它非常冗长。如果我们不能嵌套调用,我没有问题,但

假设我们要实现以下计算:

outval/err=f3(f3(f1(无效))

f1
f2
f3
中的每一个都可能失败并出现错误时,我们停止计算并将
err
设置为失败函数返回的错误。(当然,嵌套可以任意长)

在像C++/JAVA/C这样的语言中,可以通过让
f1
f2
f3
抛出异常并将计算包含在try-catch块中来轻松完成,而在像Haskell这样的语言中,我们可以使用单子

现在我正试图在GO中实现它,我能想到的唯一方法是if else ladder,它非常冗长。如果我们不能嵌套调用,我没有问题,但在我看来,在代码中的每一行之后添加错误检查看起来很难看,会破坏流程。我想知道是否有更好的方法来实现它

编辑:根据peterSO的评论进行编辑
下面是具体的示例和简单的实现

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)
}
我想说明的是,函数calc可能借助于可能失败的库函数进行一些计算,语义是如果任何调用失败,calc会将错误传播给调用方(类似于不处理异常)。在我看来,calc的代码很难看

对于所有库函数都具有完全相同的签名的特殊情况,我们可以改进代码(我使用的是idea from)

然后我们可以将calc重新定义为

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


但是如果没有泛型支持,我不知道如何将这种方法用于任何一组库函数。

错误与异常之间的讨论是一个漫长而乏味的过程。因此,我将不深入讨论它

对于您的问题,最简单的答案是Go内置的
延迟
恐慌
、和
恢复
功能,如本文所述。它们可以提供类似于异常的行为

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
}

如果没有一个具体的例子,你是在向风车倾斜。例如,根据你的定义,fn函数返回一个值和任何错误。fn函数是其签名不能更改的包函数。使用你的例子

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)
}

如何让示例编译和运行?

如果您真的想做到这一点,可以使用compose函数

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)

大多数情况下,您可能希望比这更简单,因为其他人在遇到您的代码时可能很难理解它。

首先,是您习惯的try-catch样式的扩展版本,显然借鉴了jimt的答案和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.
因此,您可能会抗议您要求的更简单,但事实并非如此。总体上没有任何争论,但如果库函数都返回错误值,并且您希望在没有错误值的情况下链接函数调用,那么可用的解决方案是包装库函数,包装器非常薄且易于编写。唯一的另一个困难部分是在deferred函数中,但这是一种可以学习和重用的模式,而且只需几行代码

我不想太多地推广这个解决方案,因为它不是一个经常使用的解决方案。尽管它是一个有效的模式,并且有一些合适的用例

正如jimt所提到的,错误处理是一个很大的主题。“在围棋中处理错误的好方法是什么?”这将是一个很好的问题,除了它不符合“整本书”标准的问题。我可以想象一本关于围棋中错误处理的整本书

相反,我将提供我的一般观察结果,如果你只是开始处理错误值,而不是试图让它们消失,过一段时间你就会开始理解这样做的好处。在我们这里使用的玩具示例中,看起来像是if语句的详细阶梯,当y时,可能仍然像是if语句的详细阶梯你首先在一个真实的程序中编写它。当你真的需要处理这些错误时,你回到代码中,突然发现它是存根,所有的存根都在等着你用真实的错误处理代码来充实。你可以看到该怎么做,因为导致错误的代码就在那里。你可以阻止用户看到一个模糊的错误w级别的错误消息,而不是显示有意义的内容。作为程序员,系统会提示您做正确的事情,而不是接受默认的事情


要获得更全面的答案,一个很好的资源是这篇文章。如果你在文章中搜索,也会有关于这个问题的长时间讨论。标准库中的函数之间会有相当多的相互调用,(惊喜)因此,标准库的源代码包含许多处理错误的示例。这些示例非常值得研究,因为这些代码是由Go作者编写的,他们正在推广这种处理错误值的编程风格。

找到了有关此主题的邮件。添加它以供参考。

太糟糕了,这一个已关闭已经……这是:

value := f(f(f(1)))
不是链接的示例,而是嵌套的示例。链接应类似于:

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

这是一个有效的解决方案。

谢谢你的回复,但我不确定我是否理解你的方法。如果我不控制f1、f2、f3等的签名,比如说因为它们是库函数,那该怎么办。我谦虚地认为,其他语言的错误处理基础设施不要求你拥有这样一个controlSuyog,引用你最初的问题,“…通过让f1、f2和f3抛出异常来完成…”。。。。"听起来您确实允许更改f1、f2和f3来抛出异常。让它们死机也没什么不同。死机是Go中可用的机制,用于从任意深度展开堆栈,并在过程中返回一个值。它不是处理Go中错误的惯用方法和首选方法,但它是可以满足您要求的机制。我的意思是f1、f2、f3已经被定义为抛出异常
value := f(f(f(1)))
c.funA().funB().funC()