匿名的Go函数可以调用自身吗?

huangapple go评论99阅读模式
英文:

Can an anonymous go function call itself?

问题

我只会为你提供翻译服务,以下是你要翻译的内容:

我只是在进行Go之旅,到了树遍历的练习。这显然是递归,但在调用栈的最后一个弹出之后关闭通道是一个特殊情况。无论如何,我最终是这样实现的:

// Walk函数遍历树t,将树中的所有值发送到通道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函数遍历树t,将树中的所有值发送到通道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)
}

这是一个简单的琐事问题,但我对这门语言还不够了解,所以找不到答案...

英文:

I'm just running through the Tour of Go, and got to the tree walker exercise. Its obvious recursion, but closing the channel is a special case after the final pop off the call stack. Anyway, I ended up implementing it thusly:

// 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 &lt;- t.Value
	    if (t.Right != nil) {
	        walker(t.Right, ch)
	    }
	}
	walker(t, ch)
	close(ch)
}

So far my impression of go is that they prefer to avoid saying things if they can, so the declaration of var walker before the definition seems off. Perhaps I missed some detail that would allow a function to refer to itself without the declaration? It would be a little nicer if it could be:

// 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 &lt;- t.Value
	    if (t.Right != nil) {
	        __me__(t.Right, ch)
	    }
	}(t, ch)
	close(ch)
}

This is a simple trivia question, but I'm new enough to the language that I am not finding the answer...

答案1

得分: 5

在初始化语句中,你不能在变量声明之前使用它,因为它还没有被声明。

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

英文:

You cannot use a variable before it is declared, and it is not yet declared inside its initialization statement.

So yes, the declaration line is required, and no, there is no way to avoid it.

答案2

得分: 3

我同意@milo-chirstiansen的观点,即在匿名函数的声明中,它无法引用自身的实例。

如果你想要写出符合Go代码惯例的感觉,可以这样写,不使用匿名函数:

// Walk函数遍历树t,并将树中的所有值发送到通道ch
func Walk(t *tree.Tree, ch chan int) {
    Walker(t, ch)
    close(ch)
}

// Walker函数递归调用,执行具体操作
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在2016年的GopherCon大会上发表了一场关于Go语言中nil概念的演讲。其中,他举了一个关于如何优雅地使用nil来解决二叉树求和问题的例子。下面是他演讲的一部分,如果你有时间的话,我建议你去看一下。
参考链接:https://youtu.be/ynoY2xz-F8s?t=16m28s

我知道你对于你的示例中的Tree结构体没有控制权,但是如果你有的话,你的代码可能会像这样:https://play.golang.com/p/iM10NQXfgw

package main

import "fmt"

// Tree是一个具有整数值的二叉树
type Tree struct {
    Left  *Tree
    Value int
    Right *Tree
}

// Walk将值加载到通道中;调用者负责提供和关闭通道
func (t *Tree) Walk(ch chan int) {

    // 非常有趣:Go语言支持在nil实例上调用函数
    if t == nil {
        return // 什么都不返回
    }

    t.Left.Walk(ch)  // 递归调用左节点的Walk函数
    ch <- t.Value
    t.Right.Walk(ch) // 递归调用右节点的Walk函数
}

func main() {
    // 树的初始值;这里我没有很符合惯例
    tree := &Tree{
        Left:  &Tree{Value: 2},
        Value: 1,
        Right: &Tree{Left: &Tree{Value: 4}, Value: 3},
    }

    ch := make(chan int)

    // 在单独的goroutine中将值加载到通道中,以防止阻塞
    go func() {
        tree.Walk(ch)
        close(ch)
    }()

    // 将通道中的每个值写入,直到所有值都被写入并且通道关闭
    for val := range ch {
        fmt.Println(val)
    }
}

> 1

> 2

> 3

> 4
英文:

I agree with @milo-chirstiansen that it isn't possible for an anonymous func to refer to its instance in its declaration.

If you're trying to get a feel for writing idiomatic Go code, it might look a bit more like this, doing away with the anonymous 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 &lt;- t.Value
	if t.Right != nil {
		Walker(t.Right, ch)
	}
}

You might find this interesting...

Go allows some interesting things that aren't possible in other languages. This sometimes requires thinking a little differently about your code.

Frances Campoy gave a talk at GopherCon 2016 about his storied history with the concept of nil in Go. One of his examples of how nil can be used beautifully and idiomatically, involved a solution to receiving the sum for a binary tree. Below is a link starting with this piece of his talk and I'd recommend checking it out if you have the time.
ref: https://youtu.be/ynoY2xz-F8s?t=16m28s

I realize you don't have control over the Tree struct in your example, but if you did, here's how your code might look: https://play.golang.com/p/iM10NQXfgw

package main

import &quot;fmt&quot;

// 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 &lt;- t.Value
	t.Right.Walk(ch) // recursively call Walk on right node
}

func main() {
	// Initial value for our tree; I&#39;m not being very idiomatic with this
	tree := &amp;Tree{
		Left:  &amp;Tree{Value: 2},
		Value: 1,
		Right: &amp;Tree{Left: &amp;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

答案3

得分: 1

这应该是可以避免的,通过结合runtime.Callerreflect.Call来实现。

但这与典型的Go语言风格相去甚远,所以我认为它并不适用于你的实际情况,尽管它确实回答了你的问题。 匿名的Go函数可以调用自身吗?

英文:

It should be possible to avoid this, by combining runtime.Caller and reflect.Call.

But this is nothing even resembling idiomatic Go, so I don't think it applies to your practical situation, although it does address the letter of your question. 匿名的Go函数可以调用自身吗?

huangapple
  • 本文由 发表于 2017年7月24日 02:03:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/45268293.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定