【问题标题】:Can an anonymous go function call itself?匿名 go 函数可以调用自己吗?
【发布时间】:2018-04-10 19:29:53
【问题描述】:

我刚刚完成了围棋巡回赛,并开始了树行者练习。其明显的递归,但关闭通道是在最终弹出调用堆栈后的特殊情况。无论如何,我最终实现了它:

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    var walker func(t *tree.Tree, ch chan int)
    walker = func(t *tree.Tree, ch chan int) {
        if (t.Left != nil) {
            walker(t.Left, ch)
        }
        ch <- t.Value
        if (t.Right != nil) {
            walker(t.Right, ch)
        }
    }
    walker(t, ch)
    close(ch)
}

到目前为止,我对 go 的印象是,如果可以的话,他们宁愿避免说话,所以在定义之前声明 var walker 似乎关闭。也许我错过了一些允许函数在没有声明的情况下引用自身的细节?如果可以的话就更好了:

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    func(t *tree.Tree, ch chan int) {
        if (t.Left != nil) {
            __me__(t.Left, ch)
        }
        ch <- t.Value
        if (t.Right != nil) {
            __me__(t.Right, ch)
        }
    }(t, ch)
    close(ch)
}

这是一个简单的琐事问题,但我对这门语言还很陌生,我找不到答案...

【问题讨论】:

  • 语言中没有这样的功能。看到这个:github.com/golang/go/issues/226
  • So far my impression of go is that they prefer to avoid saying things if they can 这是一个奇怪的印象。大多数人观察到相反的情况,事实上,这与围棋的核心哲学完全相反,即“不要隐藏复杂性”。
  • @Flimzy,诚然,我对go 很陌生,但只是在参观,比如for (i := 0; i &lt; 10; i++) -> for i := 0; i &lt; 10; i++ -> for ; sum &lt; 1000; -> for sum &lt; 1000 -> @ 987654332@, 和var thing := x*x; if x &gt; 10 -> if v := x*x; x &gt; 10... 这就是我的意思尽量避免说出来。无论如何,我明白你关于不要隐藏复杂性的观点。谢谢!
  • @icza,是的,绝对是重复的。我的googling Skillz 让我失望了。感谢您指出。

标签: go


【解决方案1】:

你不能在声明之前使用一个变量,并且它还没有在它的初始化语句中声明。

所以是的,声明行是必需的,不,没有办法避免它。

【讨论】:

    【解决方案2】:

    我同意@milo-chirstiansen 的观点,即匿名函数不可能在其声明中引用其实例。

    如果您想尝试编写惯用的 Go 代码,它可能看起来更像这样,取消匿名 func:

    // Walk walks the tree t sending all values
    // from the tree to the channel ch.
    func Walk(t *tree.Tree, ch chan int) {
        Walker(t, ch)
        close(ch)
    }
    
    // Walker does the things, made to be called recursively
    func Walker(t *tree.Tree, ch chan int) {
        if t.Left != nil {
            Walker(t.Left, ch)
        }
        ch <- t.Value
        if t.Right != nil {
            Walker(t.Right, ch)
        }
    }
    

    你可能会觉得这很有趣……

    Go 允许一些其他语言无法实现的有趣事物。这有时需要对您的代码进行一些不同的思考。

    Frances Campoy 在 GopherCon 2016 上发表了关于他在 Go 中使用 nil 概念的传奇历史的演讲。他的一个例子说明了nil 可以如何优美而惯用地使用,其中一个解决方案是接收二叉树的总和。以下是从他的这篇演讲开始的链接,如果您有时间,我建议您查看一下。 参考:https://youtu.be/ynoY2xz-F8s?t=16m28s

    我知道您无法控制示例中的 Tree 结构,但如果您这样做了,您的代码可能如下所示:https://play.golang.com/p/iM10NQXfgw

    package main
    
    import "fmt"
    
    // A Tree is a binary tree with integer values.
    type Tree struct {
        Left  *Tree
        Value int
        Right *Tree
    }
    
    // Walk loads value into channel; caller is responsible for providing and closing chan
    func (t *Tree) Walk(ch chan int) {
    
        // Super interesting: Go supports the calling of a func on a nil instance of a struct
        if t == nil {
            return // return nothing
        }
    
        t.Left.Walk(ch)  // recursively call Walk on left node
        ch <- t.Value
        t.Right.Walk(ch) // recursively call Walk on right node
    }
    
    func main() {
        // Initial value for our tree; I'm not being very idiomatic with this
        tree := &Tree{
            Left:  &Tree{Value: 2},
            Value: 1,
            Right: &Tree{Left: &Tree{Value: 4}, Value: 3},
        }
    
        ch := make(chan int)
    
        // Load values into chan in separate goroutine
        // to prevent blocking
        go func() {
            tree.Walk(ch)
            close(ch)
        }()
    
        // Write each val added to chan until all values
        // have been written and chan is closed
        for val := range ch {
            fmt.Println(val)
        }
    }
    

    1

    2

    3

    4

    【讨论】:

    • 为了清楚起见,Walker 应该是walker,对吧?您不想公开实现细节...另外,也许我很困惑,但是在您的方法示例中,不应该让 walk 出现在值之前吗?
    • Walker/walker:你可能知道,使用带有小写字母的walker 意味着函数不会被导出(即不能被另一个包调用)。这是否应该被导出取决于你计划使用这个函数,我不知道。然而,也有Walker func 是完全没有必要的,我将它包括在内只是为了展示将匿名func 从Walker 中拆分出来的微小变化如何在清晰度上有所不同,并且(从我的角度来看)增加了简单性/可维护性,这是 Go 的重点。
    • left walk before value: 嗯..我没有想到您可能想要从左到右输入值。我的背景是文件系统和在线存储,所以我总是想象从根到节点的移动——也许这不是查看 walker 的正确方法。我进行了一些研究,似乎有不同的模式可以解决这个问题。当您使用“按序”遍历时,我使用了“预购”模式。我应该遵循您已经使用的模式 - 现在对代码进行调整。
    • 甜蜜,不是吹毛求疵,只是想把成语/模式弄对,并确保我理解......谢谢你的回复
    【解决方案3】:

    应该可以通过结合runtime.Callerreflect.Call 来避免这种情况。

    但这与惯用的 Go 一点也不相似,所以我认为它不适用于您的实际情况,尽管它确实解决了您的问题。 :)

    【讨论】:

      猜你喜欢
      • 2019-04-02
      • 2020-07-05
      • 2014-04-03
      • 2021-07-09
      • 1970-01-01
      • 2012-06-28
      • 2011-05-05
      • 2013-02-23
      相关资源
      最近更新 更多