为什么在Swift中调用函数时第一个参数名不是必需的?

14

我正在学习Swift,但我觉得很奇怪的是,在调用函数时第一个参数的名称不是必需的。

func say(greeting: String, toName: String) {
    print("\greeting), \(toName)!")
}

say("Goodbye", toName: "Hollywood") // <-- why is there no "greeting" required?

所以你在问为什么语言的创造者决定走“这个”方向? - luk2302
我认为在第一个参数没有外部名称的情况下,其余参数有外部名称是不一致的。我认为最好让所有参数都使用外部名称以提高可读性。我认为他们最好改变这个设计,让所有参数都带有外部名称。 - Amr Lotfy
4个回答

21

正如其他人所说的那样,这是一个起源于 Objective-C 的风格问题。

为了理解为什么有人想要采用这种风格,请考虑一下修改过的例子:

func say(greeting: String) {
    print("\(greeting)")
}
你会这样调用它:
say("Hello!")

当你看到你使用的函数名时,可以说有一些信息是缺失的。对于一个名为 say() 的函数,你可能合理地认为这是一个可以让你说出任何话的函数。但是当你看到参数名时,很明显这个函数是用来问候的,而不是说任何话。

因此,Objective-C 建议你这样编写:

func sayGreeting(greeting: String) {
    print("\(greeting)")
}

你可以这样调用:

sayGreeting("Hello!")

现在很明显你是在打招呼。换句话说,函数名本身更清楚地描述了你在做什么。因此,sayGreeting("Hello!")say(greeting: "Hello!")更好,因为一个函数所做的关键事情应该由它的名称来描述,而不是转移到参数名上并给予次要重要性。

但是这个理由只适用于第一个参数。假设你想加一个名字,就像你已经做过的那样。在像 C 这样没有外部参数名称的语言中,你可以编写类似以下的代码:

void sayGreetingToName(char * greeting, char * person) { ...

然后像这样调用它:

sayGreetingToName("Hello", "Dave");

这种方法是可以的,但是当你有重载函数或默认值时,它很快就会崩溃,而在C语言中你没有这些。如果你想写:

func sayGreetingToName(greeting: String, name: String? = nil) {
    if let name = name {
        print("\(greeting), \(name)!")
    }
    else {
        print("\(greeting)!")
    }
}

然后这样调用它:

sayGreetingToName("Hello", "Dave")

看起来基本上还可以,但是:

sayGreetingToName("Hello")

这看起来很荒谬,因为函数名表明你会提供一个名称,但实际上并没有。

因此,如果你这样写:

func sayGreeting(greeting: String, toName: String? = nil) {
    if let name = toName {
        print("\(greeting), \(name)!")
    }
    else {
        print("\(greeting)!")
    }
}

您可以用两种方式调用它:

sayGreeting("Hello")
sayGreeting("Hello", toName: "Dave")

一切看起来非常清晰明了。

总之,这种写作风格的想法是函数名本身应该包含第一个参数名称所包含的任何信息,但将此扩展到后续参数是没有意义的。因此,默认情况下第一个参数没有外部名称,但其余参数有。该函数始终是关于说问候语,因此应该在函数名称中体现(因此不需要通过坚持第一个参数的外部名称来重复)。但它可能或可能不是关于对特定名称说出问候语,因此该信息不应包含在函数名称中。

这种风格还使您基本上可以将函数调用读作英语,因为现在名称和参数大致处于正确的顺序:

sayGreeting("Hello", toName: "Dave")

向名为"Dave"的人问候,说:“你好”

一旦你习惯了,这种方式还是相当不错的风格。


5

Chris Lattner在Swift 2中的新特性中正好谈到了这个问题:

在Swift 1.x时代,此行为仅适用于方法。在类、结构或枚举之外编写的函数在函数调用时不需要命名任何参数(除非您在函数定义中显式强制使用外部参数名)。

由于Objective-C的约定,方法通常以一种方式命名,即第一个参数已成为方法名称的一部分。
例如:indexOf(_:)而非index(of:)charactersAtIndex(_:)而非charactersAt(index:)
在Objective-C中,这将被写为indexOf:charactersAtIndex:。没有大括号来区分函数名和函数参数。因此,参数基本上是函数名称的一部分。

如前所述,但最初仅适用于方法。这导致程序员困惑,何时添加第一个参数的外部名称,何时不添加。因此,最终更改了该行为,以使第一个参数默认情况下不使用内部名称作为外部名称,但所有后续参数都会使用。
结果是更一致地使用外部和内部参数名称。这就是Swift今天的行为。

简而言之,此行为是Objective-C的遗留物


1
要挑剔一下的话:这种行为不是从 objc 本身遗留下来的,更多的是从 ObjC 使用的 API 设计约定遗留下来的。苹果也可以决定将 say(greeting: "Hello", toName: "World") 设置为默认值,并仍然保持 Cocoa 历史中调用位置读起来像英语句子的好处...但如果他们这样做了,要么a)所有导入的 ObjC 方法看起来都与本地 Swift 方法不同,要么b)他们必须使导入程序无缺地将每个 ObjC 方法(例如 sayGreeting:toName:)映射到一个 Swift 方法(例如 say(greeting:toName:))。 - rickster

4
这是Swift函数的默认行为,省略第一个函数参数的外部名称。
默认情况下,第一个参数省略其外部名称,而第二个及其后续参数使用它们的本地名称作为其外部名称。
来自Language Guide - Functions
请注意,如果需要,您也可以向第一个函数参数添加外部名称:
func foo(extBar bar: String, bar2: String) {
    print(bar+bar2)
}

foo(extBar: "Hello", bar2: "World")

同样地,您可以在函数签名中在第二个(以及后续的)函数参数的内部名称前添加 _ 以省略它们的外部名称。
func foo2(bar: String, _ bar2: String) {
    print(bar+bar2)
}

foo2("Hello", "World")

请注意,对于初始化程序,外部名称对于所有函数参数(包括第一个参数)都是强制性的。
与函数和方法参数一样,初始化参数可以具有用于初始化程序主体内部的本地名称和用于调用初始化程序时使用的外部名称。
但是,与函数和方法不同,初始化程序在其括号之前没有标识函数名称。因此,初始化程序的参数名称和类型在确定应调用哪个初始化程序方面发挥着特别重要的作用。因此,如果您未提供外部名称,则Swift会为初始化程序中的每个参数提供自动外部名称。
来自语言指南-初始化
例如,请考虑
struct Foo {
    var bar : Int

    init(extBar: Int) {
        bar = extBar
    }
}

var a = Foo(extBar: 1)

在这种情况下,您是否可以明确告诉构造函数让参数省略其外部名称。
struct Foo2 {
    var bar : Int

    init(_ intBar: Int) {
        bar = intBar
    }
}

var a = Foo2(1)

2
如@dfri所提到的,它只是这样决定的。
请注意,您可以通过在调用时显式要求参数名称并使用_来省略它们来轻松自定义该行为:
func say (greeting greeting: String, _ toName: String){
    print("\(greeting), \(toName)!")
}

结果是您需要通过调用它来使用

say(greeting: "Goodbye", "Hollywood")

或者

func say (greeting: String, _ toName: String){
    print("\(greeting), \(toName)!")
}

say("Goodbye", "Hollywood")

或者

func say (greeting greeting: String, toName: String){
    print("\(greeting), \(toName)!")
}

say (greeting: "Goodbye", toName: "Hollywood")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接