英文:
Can you declare multiple variable types for function arguments if you have multiple types to declare? (Golang)
问题
好的,以下是翻译好的内容:
假设我有这段代码(作为对真实代码的简化理解):
package fiction
import "fmt"
func doSomething(height int, width int, printMe string) int {
fmt.Println(printMe)
return height * width
}
我该如何将函数声明中的 height int, width int,
部分转换为 height, width int,
?
所以我想做的是这样的:
package fiction
import "fmt"
func doSomething(height, width int, printMe string) int {
fmt.Println(printMe)
return height * width
}
这样行得通吗?相信我,我知道这只是三个字母的差别,在我寻找答案和输入这段文字的时间里,这个问题已经没有意义了,但是我现在只是想知道...
另外,我想避免使用可变参数,但如果有一种解决方案涉及可变参数,并且这是唯一的解决方案,了解一下也是好的(尽管它对我当前的用例不适用)。
英文:
Ok so lets say i have this code (as an easy to understand abstraction of the real thing):
package fiction
import "fmt"
func doSomething(height int, width int, printMe string) int {
fmt.Println(printMe)
return height * width
}
how would I turn the height int, width int,
part in the function declaration into something like height, width int,
?
So what I'm trying to do is something like:
package fiction
import "fmt"
func doSomething(height, width int, printMe string) int {
fmt.Println(printMe)
return height * width
}
or is this just not possible? And trust me, I get it, its just 3 letters, and between the time I've spent looking for the answer and typing this out its really a moot point but at this point I just want to know...
Also I would like to stay away from variadic parameters, BUT if there is a solution that involves variadic parameters, AND its the only solution, it would be nice to know (although it won't really work for my current use case).
答案1
得分: 2
总结:是的。
我们需要弄清楚两件事:
- 语法是否有效?
- 语义是否符合你的要求?
这两点都很重要:如果语法允许你以这种方式编写代码,但它执行的操作与你想要的不同,那是没有帮助的。(例如,省略类型可能意味着类型是 any
,或者类型是从调用点推断出来的。省略类型并不明显意味着“取下一个找到的类型”。)
旁白
当在这里浏览问题时,我发现很多程序员几乎痴迷于语法:他们经常问“这个语法是否有效”,“这个的正确语法是什么”,“这个的语法会是什么”等等。然而,更重要的是:这实际上是否做到了你想要的,即是否具有所需的语义?
但我偏离了主题。
语法
回答语法是否有效的问题实际上很容易。我们只需要按照线索进行。
根据《Go编程语言规范》中的函数声明一节,函数声明的语法如下:
FunctionDecl = "func" FunctionName [ TypeParameters ] Signature [ FunctionBody ] .
FunctionName = identifier .
FunctionBody = Block .
我们感兴趣的是Signature
的语法。幸运的是,《Go编程语言规范》提供了超链接,所以我们只需要点击语法中的Signature
非终结符,就会跳转到函数类型一节,其中定义了如下语法:
FunctionType = "func" Signature .
Signature = Parameters [ Result ] .
Result = Parameters | Type .
Parameters = "(" [ ParameterList [ "," ] ] ")" .
ParameterList = ParameterDecl { "," ParameterDecl } .
ParameterDecl = [ IdentifierList ] [ "..." ] Type .
因此,Signature
是Parameters
后面跟一个可选的Result
。Parameters
是一个左圆括号,后面跟一个可选的ParameterList
(后面可能跟一个逗号),最后是一个右圆括号。ParameterList
是一个ParameterDecl
后面跟零个或多个逗号和ParameterDecl
组成的组。ParameterDecl
是一个可选的IdentifierList
后面跟一个可选的省略号和一个Type
。
这意味着我们现在需要知道IdentifierList
是什么。名称本身已经暗示了它是一个标识符列表,这意味着我们确实可以在一个类型前面放置多个标识符,但让我们确保一下。
同样的方法,点击非终结符IdentifierList
,我们来到了常量声明一节,我们可以看到IdentifierList
是一个标识符后面跟零个或多个逗号和一个标识符:
IdentifierList = identifier { "," identifier } .
换句话说,IdentifierList
就是它的名字所暗示的那样:由逗号分隔的标识符列表。
如果我们将所有定义都代入我们最初开始的函数签名参数的语法中,我们得到以下结果:
Parameters = "(" [ ParameterList [ "," ] ] ")" .
代入ParameterList
的定义:
Parameters = "(" [ ParameterDecl { "," ParameterDecl } [ "," ] ] ")" .
代入ParameterDecl
的定义:
Parameters = "(" [ [ IdentifierList ] [ "..." ] Type { "," [ IdentifierList ] [ "..." ] Type } [ "," ] ] ")" .
代入IdentifierList
的定义:
Parameters = "(" [ [ identifier { "," identifier } ] [ "..." ] Type { "," [ identifier { "," identifier } ] [ "..." ] Type } [ "," ] ] ")" .
我们可以看到你的参数列表确实是一个符合语法的参数列表。
语义
所以,现在的问题是:这个语法的含义是什么,即它的语义是什么,而且这些语义是否符合你的期望?
对于signature的语义定义,函数声明一节再次链接到函数类型一节,后者有以下说明[加粗斜体为我所强调]:
在参数或结果列表中,名称(IdentifierList)要么全部存在,要么全部不存在。如果存在,每个名称代表指定类型的一个项(参数或结果)[...]。
因此,这意味着,确实,当你写一个IdentifierList
后面跟一个Type
时,这意味着该IdentifierList
中的所有参数都具有Type
指定的类型。
在你的情况下,这意味着height
和width
都具有类型int
。
英文:
tld;dr summary: Yes.
We need to figure out two things:
- Is the syntax valid?
- Do the semantics do what you want?
Both of these are important: it doesn't help you if the syntax allows you to write code that way, but it does something different from what you want. (For example, leaving out the type could mean that the type is any
or that the type is inferred from the call-site. It is not obvious that leaving out the type means "take the next type you find".)
Aside
When browsing questions here on SO, I find that a lot of programmers are almost obsessed with syntax: they constantly ask "is this syntax valid", "what is the correct syntax for this", "what would be the syntax for this", etc. When what is, arguably, more important is: does this actually do what you want, i.e. does it have the required semantics?
But I digress.
Syntax
The question whether the syntax is valid is actually easy to answer. We just need to follow the breadcrumbs.
According to the section Function declarations of the Go Programming Language Specification, the syntax for a function declaration is:
> lang-none
> FunctionDecl = "func" FunctionName [ TypeParameters ] Signature [ FunctionBody ] .
> FunctionName = identifier .
> FunctionBody = Block .
>
What we are interested in, is the syntax for the Signature
. Thankfully, the Go Programming Language Specification is hyperlinked, so all we need to do is click on the Signature
non-terminal in the grammar which brings us to the section Function types, where the syntax is defined like this:
> lang-none
> FunctionType = "func" Signature .
> Signature = Parameters [ Result ] .
> Result = Parameters | Type .
> Parameters = "(" [ ParameterList [ "," ] ] ")" .
> ParameterList = ParameterDecl { "," ParameterDecl } .
> ParameterDecl = [ IdentifierList ] [ "..." ] Type .
>
So, a Signature
is Parameters
followed by an optional Result
. Parameters
is an opening round parenthesis followed by an optional ParameterList
(itself followed by an optional comma) followed by a closing round parenthesis. A ParameterList
is a ParameterDecl
followed by zero or more groups of a comma followed by a ParameterDecl
. A ParameterDecl
is an optional IdentifierList
followed by an optional ellipsis followed by a Type
.
Which means we now need to know what an IdentifierList
is. The name sort-of already implies that it is a list of identifiers, which would mean that, indeed, we can put multiple identifiers in front of a single type, but let's make sure.
Same game, the non-terminal IdentifierList
is hyperlinked and takes us to the section Constant declarations, where we see that an IdentifierList
is an identifier
followed by zero or more groups of a comma followed by an identifier:
> lang-none
> IdentifierList = identifier { "," identifier } .
>
In other words, an IdentifierList
is what it sounds like: a list of identifiers, separated by commas.
If we substitute all our definitions back into the grammar for the parameters in a function signature we started out with, we get the following:
Parameters = "(" [ ParameterList [ "," ] ] ")" .
Substitute the definition of ParameterList
:
Parameters = "(" [ ParameterDecl { "," ParameterDecl } [ "," ] ] ")" .
Substitute the definition of ParameterDecl
:
Parameters = "(" [ [ IdentifierList ] [ "..." ] Type { "," [ IdentifierList ] [ "..." ] Type } [ "," ] ] ")" .
Substitute the definition of IdentifierList
:
Parameters = "(" [ [ identifier { "," identifier } ] [ "..." ] Type { "," [ identifier { "," identifier } ] [ "..." ] Type } [ "," ] ] ")" .
And we can see that your parameter list is indeed a syntactically valid parameter list.
Semantics
So, the question now becomes: what does this syntax mean, i.e. what are its semantics and are those the semantics you expect?
For the semantic definition of the signature, the section on Function declarations links again to the section on Function types, which has this to say [bold italic emphasis mine]:
> Within a list of parameters or results, the names (IdentifierList) must either all be present or all be absent. If present, each name stands for one item (parameter or result) of the specified type […]
So, what this is saying is that, indeed, when you write an IdentifierList
followed by a Type
, that means all parameters in that IdentifierList
have the type specified by Type
.
In your case, that means that both height
and width
have the type int
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论