在Go语言中使用递归引用

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

Using Recursive References in Go

问题

我想将所有的命令都包含在一个映射中,并将命令映射到执行任务的函数(就像标准的分派表)。我从以下代码开始:

package main

import "fmt"

func hello() {
    fmt.Print("Hello World!")
}

func list() {
    for key, _ := range whatever {
        fmt.Print(key)
    }
}

var whatever = map[string](func()) {
    "hello": hello,
    "list": list,
}

然而,它无法编译,因为函数和结构之间存在递归引用。尝试前向声明函数会导致错误,因为在定义函数时会重新定义,而且映射位于顶层。如何在顶层定义和初始化这样的结构,而不需要使用init()函数呢?

我在语言定义中找不到一个好的解释。

  • 存在的前向引用是针对“外部”函数的,当我尝试前向声明函数时,它无法编译。
  • 我也找不到前向声明变量的方法。

更新: 我正在寻找一种解决方案,它不需要在程序启动时或在init()函数中显式地填充变量。不确定是否可能,但在我所知的所有可比较的语言中都可以实现。

更新2: FigmentEngine 提出了一种方法,我在下面的答案中给出了这种方法。它可以处理递归类型,并且还允许对所有命令的映射进行静态初始化。

英文:

I want to contain all my commands in a map and map from the command to a function doing the job (just a standard dispatch table). I started with the following code:

package main

import "fmt"

func hello() {
	fmt.Print("Hello World!")
}

func list() {
	for key, _ := range whatever {
		fmt.Print(key)
	}
}

var whatever = map[string](func()) {
	"hello": hello,
	"list": list,
}

However, it fails to compile because there is a recursive reference between the function and the structure. Trying to forward-declare the function fails with an error about re-definition when it is defined, and the map is at top-level. How do you define structures like this and initialize them on top level without having to use an init() function.

I see no good explanation in the language definition.

  • The forward-reference that exists is for "external" functions and it does not compile when I try to forward-declare the function.
  • I find no way to forward-declare the variable either.

Update: I'm looking for a solution that do not require you to populate the variable explicitly when you start the program nor in an init() function. Not sure if that is possible at all, but it works in all comparable languages I know of.

Update 2: FigmentEngine suggested an approach that I gave as answer below. It can handle recursive types and also allow static initialization of the map of all commands.

答案1

得分: 2

正如你可能已经发现的那样,Go规范中指出(我强调):

如果A的初始化器依赖于B,那么B将在A之前设置。依赖分析不依赖于正在初始化的项的实际值,只依赖于它们在源代码中的出现。如果A的值包含对B的提及,包含一个其初始化器提及B的值,或者_提及一个提及B的函数_,则A依赖于B,递归地。如果这样的依赖形成一个循环,则会出现错误。

所以,不,你试图做的是不可能的。Issue 1817提到了这个问题,Russ Cox确实说Go中的方法可能偶尔过于限制性。但它是清晰和明确定义的,并且有可用的解决方法。

所以,解决这个问题的方法仍然是使用init()。抱歉。

英文:

As you might already have found, the Go specifications states (my emphasis):

>if the initializer of A depends on B, A will be set after B. Dependency analysis does not depend on the actual values of the items being initialized, only on their appearance in the source. A depends on B if the value of A contains a mention of B, contains a value whose initializer mentions B, or mentions a function that mentions B, recursively. It is an error if such dependencies form a cycle.

So, no, it is not possible to do what you are trying to do. Issue 1817 mentions this problem, and Russ Cox does say that the approach in Go might occasionally be over-restrictive. But it is clear and well defined, and workarounds are available.

So, the way to go around it is still by using init(). Sorry.

答案2

得分: 1

根据上面FigmentEngine的建议,实际上可以创建一个静态初始化的命令数组。但是,你需要预先声明一个传递给函数的类型。我给出了下面重写的示例,因为它可能对其他人有用。

我们将新类型称为Context。它可以包含如下的循环引用。

type Context struct {
    commands map[string]func(Context)
}

完成后,可以像这样在顶层声明数组:

var context = Context {
    commands: map[string]func(Context) {
        "hello": hello,
        "list": list,
    },
}

请注意,完全可以引用文件中后面定义的函数,因此我们现在可以引入这些函数:

func hello(ctx Context) {
    fmt.Print("Hello World!")
}

func list(ctx Context) {
    for key, _ := range ctx.commands {
        fmt.Print(key)
    }
}

完成后,我们可以创建一个主函数,该函数将调用在声明的上下文中的每个函数:

func main() {
    for key, fn := range context.commands {
        fmt.Printf("Calling %q\n", key)
        fn(context)
    }
}
英文:

Based on the suggestion by FigmentEngine above, it is actually possible to create a statically initialized array of commands. You have, however, to pre-declare a type that you pass to the functions. I give the re-written example below, since it is likely to be useful to others.

Let's call the new type Context. It can contain a circular reference as below.

type Context struct {
	commands map[string]func(Context)
}

Once that is done, it is possible to declare the array on top level like this:

var context = Context {
	commands: map[string]func(Context) {
		"hello": hello,
		"list": list,
	},
}

Note that it is perfectly OK to refer to functions defined later in the file, so we can now introduce the functions:

func hello(ctx Context) {
	fmt.Print("Hello World!")
}

func list(ctx Context) {
	for key, _ := range ctx.commands {
		fmt.Print(key)
	}
}

With that done, we can create a main function that will call each of the functions in the declared context:

func main() {
	for key, fn := range context.commands {
		fmt.Printf("Calling %q\n", key)
		fn(context)
	}
}

答案3

得分: 0

在使用list()之前,只需在函数内部填充地图即可。就像这样

抱歉,我没有看到你写的“不使用init()”:这是不可能的。

英文:

Just populate the map inside a function before using list(). Like that.

Sry I did not see that you wrote "without init()": that is not possible.

huangapple
  • 本文由 发表于 2013年10月28日 18:54:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/19632841.html
匿名

发表评论

匿名网友

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

确定