如果Swift还要求参数顺序,为什么它还要求参数名?

yr9zkbsy  于 2023-01-04  发布在  Swift
关注(0)|答案(5)|浏览(180)

在Swift中,当你调用一个函数时,你需要给参数加上标签,除非函数的作者明确允许你不这样做。从语言设计的Angular 来看,这有什么原因吗?我一直认为参数标签是一种很好的方式,可以让调用者以任何有意义的方式对参数进行排序,但这种方式感觉像是一堆毫无意义/无用的样板文件。
例如,如果我们像这样定义函数makeDog:

makeDog(legs: int, name: String)->Dog{}

那么它必须这样命名:

makeDog(legs: 4, name: "fido")

而且它***不能***这样调用(编译器错误):

makeDog(name: "fido", legs: 4)

甚至named-parameters的Stack Overflow标记说明也是这样说的:
命名参数使您能够通过将实参与参数的名称相关联而不是与参数列表中的参数位置相关联来为特定参数指定实参。

zsohkypk

zsohkypk1#

靶区; DR

为了帮助理解你的问题的答案,你首先要明白,与其他支持命名参数的语言不同,在Swift中,你实际上根本看不到参数名称。你看到的是所谓的外向标签。虽然它们经常与参数名称匹配,但这仅仅是一种巧合/便利。它们仍然是非常独立的东西,具有完全不同的规则集。理解这种差异有助于解释你问题的答案。
这就是为什么在Swift中,当你提到调用点时,根本不要考虑参数名称,而要考虑你看到的***单词,它们通过帮助调用点读起来更像一个句子***来增加清晰度,就像你不能随机地重新排序句子中的单词一样,你也不应该随机地重新排序这些标签。

规则

描述上述内容的一套简单规则可归纳如下......

  • 参数标签(外部)意味着 * 仅 * 在调用点有意义,这样你传递的内容就很清楚了。因此,它们可能更多地以"语法"的方式而不是"标识符"的方式工作(例如,函数add(4, to: 3)对第二个参数使用标签"to:",而对第一个参数根本不使用标签,因为函数名本身完成了语法)。此外,标签 * 不必 * 是唯一的,因为它们也是由序数位置绑定的。
  • 参数名(内部)意味着 * 仅 * 在函数/方法的内部主体上下文中有意义。上面的函数可以写成add(_ valueA: Int, to valueB: Int),它仅在函数的实现中使用valueAvalueB
  • 如果这样的名称在两个上下文中都有意义,那么只需要指定参数名称,它就会隐式地为标签使用相同的名称(例如,someFunc(a: Int)等价于someFunc(a a: Int)

我强烈建议查看Swift.orgAPI Design Guidelines,因为它更详细地说明了为什么做出这些决定。

详细信息(例如原因)

在大多数语言中,没有外向标签的概念,你只是简单地通过序数位置指定函数的参数值,通常用逗号分隔。
现在在一些语言中,比如C#,除了按顺序指定参数值,你还可以用参数名来指定它们。如果你用名称指定所有参数,那么逻辑上讲,你可以用任何你想要的顺序来指定它们,但是没有使用名称的要求,所以你可以混合和匹配序数。
但是如果你可以这样做,当你指定一些序数和一些名称时会发生什么呢?现在你被迫把命名的放在最后,因为如果你不这样做,它怎么知道未命名参数的位置呢?或者更令人困惑的是,如果你在第三个位置指定了一个参数值,但是你后来又指定了同一个参数的名称,会发生什么呢?这会让人困惑。
相比之下,Swift不仅力求在调用约定上更加一致,而且还力求成为一种自文档语言,重点在于使用时的清晰性这样,Swift增加了默认为必需的外部/外向标签的概念,但是如果有意义的话,它允许你把它们设为可选的,如果你没有为你的参数显式地指定一个标签,标签会被隐式地设为和参数同名,但是同样的,它们仍然是非常不同的东西。
至于为什么这是一件好事,让我们看一些例子。

示例

让我们从一种允许重新排序命名参数的语言开始。

var result = multiplyValues(valA: 20, valB: 5)

既然你可以重新订购,你也可以写这个...

var result = multiplyValues(valB: 5, valA: 20)

现在在Swift中,你在调用点使用的不是参数 * 名称 *,而是标签,它们在签名本身的整体上下文中提供帮助。
考虑一下这个稍微复杂一些的函数定义...

func multiply(value: Int, by factor: Double, thenAdd addlAmount: Int){}

当被调用时,它读起来像这样...

let result = multiply(value: 20, by: 1.5, thenAdd: 5)

这不是比另一个例子更清楚吗?但是需要注意的是,***'by'不是参数名,而是一个标签,根据它在调用中的位置,它是有意义的。***实际的底层参数称为'factor',这是在函数实现的主体中使用的。
按照Swift的设计原则,甚至可以将第一个参数的标签设置为可选,现在的标签如下所示...

let result = multiply(20, by: 1.5, thenAdd: 5)

再一次,这是非常清楚的事情,几乎读起来像一个句子。
现在想象一下,如果你不使用这些标签,而只使用参数名,现在参数名在外部环境中很重要,例如,看这个

let result = multiply(value: 20, addlAmount: 5, factor: 1.5)

清晰度开始变得模糊。你是用20乘以1.5再加上5,还是用25乘以1.5?答案是35还是37.5?
如果您使用外部名称/标签呢?现在情况更糟了!

let result = multiply(by: 1.5, thenAdd: 5, 20)

这到底是怎么回事?

当你用这种方式重新排序它们时,你并不清楚你用1.5乘以了多少,因为如果这是在一个实体(结构体、类等)中,很容易被误认为你用1.5乘以了隐式的self(即self.multiply(by:)),那么在没有上下文的情况下,在最后有一些随机参数20,同样也不清楚。
当然,您可能会自然地回答“但这是一个可选的标签!当且仅当必须指定 * 所有 * 标签时,顺序不应该是重要的。”但现在您将规则弄得一团糟,并破坏了单个特定用例的一致性。
也许一个更相关的问题是,你实际上得到了什么 * 与 * 一次性用例?与所有这些现有规则的优点,什么是缺点?你不能做什么,不允许随机秩序?
正是上述对不一致性的厌恶导致Swift在早期版本中引入了一个突破性的变化,即第一个参数的标签是不需要的,即使你指定了一个。如果你愿意的话,它隐式地添加了一个_。但是为什么第一个参数在与其他参数相比时会得到特殊对待呢?答案是......不应该!所有这些都让人们感到困惑,所以他们在未来的版本中改变了它,因为再一次,一致性比聪明更重要,甚至超过了引入一个突破性的改变。

参数名必须唯一。标签名不唯一。

由于标签名称是为了在调用点提供清晰度,帮助它读起来更像一个句子,所以可以多次使用同一个标签,因为顺序也很重要。
但是参数名必须是唯一的,因为它们需要在实现中共存。
下面是一个例子。注意标签“and”是如何使用两次的,但是参数名“a”、“b”和“c”必须是唯一的...

func add(_ a: Int, and b: Int, and c: Int) -> Int {
    return a + b + c
}

let result = add(1, and:2, and:3)

print(result)

总结一下...

面向外部的标签应该只在调用点才有意义,并且应该努力完成句子的语法。然而,参数名应该只在函数实现的上下文中才有意义。
遵循这些简单的指导原则,就会非常清楚什么应该放在哪里,以什么顺序,以及为什么不能简单地在调用点重新排序参数是一件好事。

vtwuwzda

vtwuwzda2#

斯威夫特这样做有几个原因。
一个是Objective-C对语言设计者的影响,命名参数是Objective-C语言的一个特性(其他语言也是)。
另一个原因是使用命名参数允许代码自记录。
如果命名参数可以按任何顺序指定,就像在其他语言中一样,这将是非常有趣的。
您可以在此处阅读有关Swift函数的详细信息:Functions

wlsrxk51

wlsrxk513#

名称用于阐明每个目的。
在Swift中,参数可以是匿名的,你可以使用它们的序号来使用它们,你通常在闭包中看到它们,在Swift中,* 函数就是闭包 *。
所以秩序很重要,名字是为了更清楚。
想象一下[Int]上的sorted函数:

[1, 2].sorted(by: <#(Int, Int) throws -> Bool#>)

你怎么知道这两个参数中的哪一个应该在<运算符的左边还是右边?它们的顺序!
对于自文档化代码,您可以将它们命名为firstsecond,以防止在函数中丢失它们的顺序:

[1, 2].sorted { first, second -> Bool in
    return first < second
}

但你可能知道,你可以去掉名字,只使用订单号:

[1, 2].sorted {
    return $0 < $1
}

正如您所知,<是Swift中的中缀 * 函数 *,如下所示:

func <(lhs: Int, rhs: Int)->Bool { return lhs < rhs }

因此,您可以将其名称作为sorted的参数传递给整个函数:

[1, 2].sorted(by: <)

它的参数有名字,但是你用了多少次?**零!**那么编译器怎么知道你传递的时候用哪一个呢?

函数签名!

参数的类型、顺序以及函数返回值的类型

wkyowqbh

wkyowqbh4#

理解这一点的方法是认识到标签的目的 * 不是 * 告诉编译器这是哪个参数,顺序和类型,结合默认值,可以做到这一点。
标签的目的纯粹是命令 * 调用者 * 调用者必须说什么。
例如:

func f(p1 param1:String = "one", p2 param2:Int = 2) {}

f(p1:"hey", p2:42)
f(p1:"hey")
f(p2:42)
f()
// but not:
// f(p2:42, p1:"hey")

标签p1:并不意味着,"嘿,编译器,这是param1参数。"
它的意思是,"嘿,调用者,如果您包含这个参数(如果您包含两个参数,它必须在前面),您必须将它标记为p1:。"

c0vxltue

c0vxltue5#

还有一件事没有提到,那就是函数或初始化器有多个带默认值的参数,而你只想覆盖其中的一个或两个默认值。例如:

func foo(aString: String = "", anInt: Int = 42, aFloat: Float = 10) {}

foo(aFloat: 50) // Skip the params where you want to use the defaults.

相关问题