根据返回参数的数量来改变行为,类似于类型断言。

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

Changing behavior based on number of return arguments like type assertions

问题

我一直在学习Go语言,其中一个特别有趣的地方是类型断言的行为如何根据捕获的返回值数量而改变:

var i interface{} = "hello"

val, ok := i.(int) // 一切正常
fmt.Println(val, ok)

val = i.(int) // 引发恐慌
fmt.Println(val)

这种模式似乎对于用户定义的函数非常有用。用户要么必须显式获取第二个返回值"ok",要么使用下划线将其忽略。无论哪种情况,他们都清楚地表明他们知道函数可能失败。而如果他们只获取一个返回值,可能会导致静默失败。因此,如果用户没有检查错误(如果错误应该"永远"不会发生,这是合理的),那么引发恐慌或类似的行为似乎是合理的。我猜这就是语言开发者在设计类型断言时的逻辑。

但是当我试图找出如何实现这一点时,却找不到任何信息。我知道类型断言不是一个实际的函数。而且许多具有多个返回值的语言无法检查实际使用的返回值数量(我只知道MATLAB可以),但是大多数这些语言都没有使用类型断言所展示的行为。

所以,是否可能实现这一点?如果可能,如何实现?如果不可能,是否有特定的原因导致这种行为被排除在内,尽管使用内置的类型断言是可能的?

英文:

I've been learning Go and one thing that stood out as particularly interesting to me is the way that the behavior of type assertions changes based on how many return values are being captured:

var i interface{} = "hello"

val, ok := i.(int) // All good
fmt.Println(val, ok)

val = i.(int) // Panics
fmt.Println(val)

This feels like a pattern that can be very useful for user defined functions. The user either has to explicitly get the "ok" second return value or use an underscore to ignore it. In either case, they're making it clear that they're aware that the function can fail. Whereas if they just get one return value, it could silently fail. Hence, it seems reasonable to panic or similar if the user isn't checking for an error (which would be reasonable if the error should "never" happen). I assume that's the logic behind the language developers in making type assertions work this way.

But when I tried to find out how that could be done, I found nothing. I'm aware that type assertions aren't an actual function. And many languages with multiple return values can't check how many return values are actually being used (MATLAB is the only one I'm aware of), but then again, most of those don't use behavior like the type assertions demonstrate.

So, is it possible and if so, how? If not, is there a particular reason that this behavior was excluded despite it being possible with the built in type assertions?

答案1

得分: 2

很遗憾,它们不能在普通函数中使用。据我所知,只有类型断言、映射值访问和范围允许使用它。

通常,当你想要一个带有一个可选的第二个错误参数的函数时,你可以这样命名它们:

func DoSomething() (string, error) {...} // 我将返回一个错误
func MustDoSomething() string {...}  // 我将引发 panic

一个例子可以参考 https://golang.org/pkg/regexp/#MustCompile

英文:

Sadly they cannot be used in normal functions. As far as i know only type assertions, map value access and range allow it.

Usually when you want to have a function with one and optional a second error argument you name them like

func DoSomething() (string, error) {...} // i will return an error
func MustDoSomething() string {...}  // i will panic

An example would be https://golang.org/pkg/regexp/#MustCompile

答案2

得分: 1

这个答案:https://stackoverflow.com/a/41816171/10278 由 @christian 提供了关于如何模拟“根据结果数量重载”的最佳实用建议。

我的目标是解决问题的另一部分——这部分:“但是当我试图找出如何做到这一点时,我什么都找不到”。

下面解释了如何在Go中实现类型断言。


在Go中,类型断言的调用行为好像是根据结果数量进行重载

然而,Go不支持方法和运算符的重载

通过查看Go的实现,我们可以知道类型断言好像是根据结果数量进行重载的原因:

  • Go 编译器提供了特殊处理,这是针对这些内置操作的。

这种特殊的分派是针对内置类型断言概念的,因为编译器正在提供一种对非内置代码不可用的特殊逻辑。

Go编译器和运行时是用Go编写的。这使得我(在某种程度上)很容易发现编译器是解释这种行为的关键。

看一下编译器的这部分代码:

代码注释已经透露了很多信息:

// dottype generates SSA for a type assertion node.
// commaok indicates whether to panic or return a bool.
// If commaok is false, resok will be nil.

通过使用调试器来逐步执行一些类型断言代码,我们可以进一步了解。

这个playground代码片段为例。特别是这些行:

object_as_closer_hardstop     := thing.(io.Closer) // will panic!!
object_as_closer,      ok     := thing.(io.Closer)

(如果你从源代码构建Go,)如果你使用调试器进入第一个类型断言,你将会在Go运行时的以下代码处停下来:

如果你进入第二个类型断言,你会停在:

在第438行,你会看到func assertI2I(只有一个返回值)。稍微往下一点,在第454行,你会看到assertI2I2。注意这两个函数的名字几乎相同,但不完全相同!

第二个函数的名字末尾有一个2。这个函数也有两个返回结果

正如我们所期望的:

(查看iface.go中的函数体,并注意哪个包含panic。)

assertI2IassertI2I2遵守我们期望的重载规则。如果它们只是根据结果数量不同,那么我们从源代码编译Go时将无法编译Go运行时,因为会出现编译器错误,比如“assertI2I redeclared”。

语言的使用者通常不知道这些内置运行时函数,因此从表面上看,这两行代码似乎调用了同一个函数:

object_as_closer_hardstop     := thing.(io.Closer) // will panic!!
object_as_closer,      ok     := thing.(io.Closer)

然而,在编译时,编译器会根据是否找到了“commaok”这种情况进行分支:

我们自己的最终用户代码无法修改Go的词法分析/解析/AST遍历,以便根据“commaok”来调度我们的函数的不同版本。

无论好坏,这就是为什么用户编写的代码无法利用这种模式的原因。

英文:

This answer: https://stackoverflow.com/a/41816171/10278 by @christian provides the best practical advice for how to emulate the "overloaded-on-result-count" pattern.

My aim is to address another part of the question—this part: "But when I tried to find out how that could be done, I found nothing".

The following explains how it is done for Go type assertions.


Invocations of type assertions in Go behave as though they are overloaded based on number of results.

Yet, Go does not support overloading of methods and operators.

Looking at Go's implementation, here is the reason type assertions appear to be overloaded based on number of results:

  • The Go compiler provides special handling that is peculiar to these built-in operations.

This special dispatching occurs for the built-in concept of type assertions because the compiler is carving out special logic that is not available to non-built-in code.

The Go compiler and runtime are written in Go. That made it (somewhat) easy for me to discover that the compiler is the key to explaining this behavior.

Take a look at this part of the compiler:

The code comment already reveals a lot:

// dottype generates SSA for a type assertion node.
// commaok indicates whether to panic or return a bool.
// If commaok is false, resok will be nil.

We can go further by using a debugger to step through some type assertion code.

Take this playground snippet for example. Specifically, these lines:

object_as_closer_hardstop     := thing.(io.Closer) // will panic!!
object_as_closer,      ok     := thing.(io.Closer)

(If you build Go from source, then) if you use a debugger to step into the first type assertion, you will end up at the following code in the Go runtime:

If you step into the second one, you end up at:

On line 438, you see func assertI2I (with a single return value). A bit lower, on line 454, you see assertI2I2. Note that these two functions have nearly identical names, but not quite!

The second function has a trailing 2 at the end of its name. That function also has two returned results.

As we expect:

(Look at the function bodies in iface.go and note which contains panic.)

assertI2I and assertI2I2 abide by the overloading rules we expect. If they were to differ only by number of results, then those of us who compile Go from source would be unable to compile the Go runtime, due to a compiler error such as "assertI2I redeclared".

Users of the language are generally not aware of these builtin runtime functions, so on the surface, both lines of code seem to call the same function:

object_as_closer_hardstop     := thing.(io.Closer) // will panic!!
object_as_closer,      ok     := thing.(io.Closer)

However, at compile time the compiler branches based on whether it found the case "commaok":

Our own end-user code does not get to modify Go's lexing/parsing/AST-walking in order to dispatch different flavors of our functions based on "commaok".

For better or for worse, that is why user-written code cannot leverage this pattern.

huangapple
  • 本文由 发表于 2017年1月24日 05:22:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/41815886.html
匿名

发表评论

匿名网友

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

确定