为啥函数调用需要 Swift 中的参数名称?

Posted

技术标签:

【中文标题】为啥函数调用需要 Swift 中的参数名称?【英文标题】:Why does a function call require the parameter name in Swift?为什么函数调用需要 Swift 中的参数名称? 【发布时间】:2014-07-25 14:27:27 【问题描述】:

我在一个类中有这个函数:

func multiply(factor1:Int, factor2:Int) -> Int
    return factor1 * factor2

我尝试使用这个来调用函数:

var multResult = calculator.multiply(9834, 2321)

问题在于编译器希望它看起来更像这样:

var multResult = calculator.multiply(9834, factor2: 2321)

为什么第一个会导致错误?

【问题讨论】:

看起来编译器认为函数是一种方法。 该类是否来自 Cocoa 类(例如 NSObject)?是否标有@objc?如果是这样,编译器假定它可以从 Objective-C 调用,因此它的方法必须是与 Objective-C 方法调用/命名约定兼容的形式。 不,它是一个根类(没有超类)并且没有用@objc 标记。 可以使第二个参数名称可选,只需将_ 放在factor2 前面,例如func multiply(factor1:Int, _ factor2:Int) .... 如果您不喜欢输入参数名称,您可以将 func multiply(factor1:Int, factor2:Int) 替换为 func multiply(factor1:Int, _ factor2:Int) 这不是 hack,而是官方语言语法 【参考方案1】:

关于将方法作为不返回值的参数传递的注意事项:

func refresh(obj:Obj, _ method: (Obj)->Void = setValue) 
    method(element)

func setValue(obj:Obj)
    obj.value = "someValue"

refresh(someObj,setValue)

【讨论】:

【参考方案2】:

函数调用中的参数名称称为关键字名称,它们的根源可以追溯到Smalltalk语言。

类和对象经常从其他地方重复使用,或者构成非常大的复杂系统的一部分,并且一次不会长期受到积极的维护关注。

在这些情况下,提高代码的清晰度和易读性非常重要,因为当开发人员面临截止日期压力时,代码通常会成为唯一的文档。

为每个参数指定一个描述性的关键字名称,使维护人员可以通过查看函数调用来快速了解函数调用的目的,而不是深入研究函数代码本身。它使参数的隐含含义明确。

在函数调用中为参数采用关键字名称的最新语言是 Rust (link) - 被描述为“一种运行速度极快、防止段错误并保证线程安全的系统编程语言。”

正常运行时间长的系统需要更高的代码质量。关键字名称让开发和维护团队有更多机会避免因发送错误参数或乱序调用参数而捕获错误。

它们可以是冗长的或简洁的,但 Smalltalkers 更喜欢冗长和描述性的而不是简洁和无意义的。他们可以负担得起,因为他们的 IDE 将为他们完成大部分此类输入工作。

【讨论】:

这是一个很好的解释。关于语言设计,这并不能证明增加的冗长是合理的,因为我们可以通过 Alt + Left Click 看到参数名称和方法描述。【参考方案3】:

原因是历史性的。这就是它在 Smalltalk 中的工作方式,并在其后代中幸存下来。吱吱,Scratch,Blockly,Objective C 和 Swift。

儿童语言(Squeak、Scratch 和 Blockly)坚持使用它,因为初学者往往会在数量和参数顺序方面遇到困难。这就是 Smalltalk 这样做的最初原因。我不知道为什么 ObjC 和 Swift 决定采用该约定,但他们确实这样做了。

【讨论】:

吱吱声不是“儿童语言”。它是一种功能齐全的语言、VM、GUI 环境和 IDE,人们已经使用来实现对儿童友好的系统,例如 Scratch、E-Toys 和 Dr-Geo,这是一种数学函数可视化器,用于教授高级学校数学.. 它还被用于实现: Seaside web 框架 - 一个框架消除了人们在浏览器中使用“后退”和“前进”按钮的所有问题;混蛋数据库;还有更多。【参考方案4】:

Swift 2.0 更新:现在函数的行为与方法相同,并且默认情况下两者都是:

第一个参数没有外部名称;和 其他参数的外部名称与内部名称相同。

除此之外,以下规则仍然适用,只是 # 速记语法现已不复存在。


这里有一个更一般的答案:函数在定义为类外的真正函数和定义为方法时表现不同。此外,init 方法有一个特殊的规则。


功能

假设你定义这个:

func multiply1(f1: Double, f2: Double) -> Double 
    return f1 * f2

这里的参数名称只是函数的本地,调用函数时不能使用:

multiply1(10.0, 10.0)

如果你想在调用函数时强制使用命名参数,你可以。在每个参数声明前加上其 external 名称。这里,f1 的外部名称是f1param,对于f2,我们使用简写,在其前面加上#,表示本地名称也将用作外部名称:

func multiply2(f1param f1: Double, #f2: Double) -> Double 
    return f1 * f2

那么,必须使用命名参数:

multiply2(f1param: 10.0, f2: 10.0)

方法

方法不同。正如您所发现的,默认情况下,除了第一个参数之外的所有参数都已命名。假设我们有这个,并考虑multiply1 方法:

class Calc 
    func multiply1(f1: Double, f2: Double) -> Double 
        return f1 * f2
    
    func multiply2(f1param f1: Double, f2: Double) -> Double 
        return f1 * f2
    
    func multiply3(f1: Double, _ f2: Double) -> Double 
        return f1 * f2
    

然后,您必须使用第二个(以及后续,如果有的话)参数的名称:

let calc = Calc()
calc.multiply1(1.0, f2: 10.0)

您可以通过为其提供外部名称来强制为第一个参数使用命名参数,例如函数(或者如果您想使用与其本地名称相同的外部名称,则在其本地名称前加上 # 前缀) .然后,你必须使用它:

calc.multiply2(f1param: 10.0, f2: 10.0)

最后,你可以为后面的其他参数声明一个外部名称_,表示你想在不使用命名参数的情况下调用你的方法,像这样:

calc.multiply3(10.0, 10.0)

互操作性说明:如果你在class Calc前面加上@objc注解,那么你可以在Objective-C代码中使用它,它相当于这个声明(查看参数名称):

@interface Calc
- (double)multiply1:(double)f1 f2:(double)f2;
- (double)multiply2WithF1param:(double)f1 f2:(double)f2;
- (double)multiply3:(double)f1 :(double)f2;
@end

初始化方法

init 方法的规则略有不同,默认情况下,所有参数都有一个外部名称。例如,这有效:

class Calc 
    init(start: Int) 
    init(_ start: String) 


let c1 = Calc(start: 6)
let c2 = Calc("6")

在这里,您必须为接受Int 的重载指定start:,但对于接受String 的重载,您必须省略它。

互操作性说明:这个类会像这样导出到 Objective-C:

@interface Calc
- (instancetype)initWithStart:(NSInteger)start __attribute__((objc_designated_initializer));
- (instancetype)init:(NSString *)start __attribute__((objc_designated_initializer));
@end

关闭

假设你定义一个像这样的闭包类型:

typealias FancyFunction = (f1: Double, f2: Double) -> Double

参数名称的行为与方法中的名称非常相似。调用闭包时必须为参数提供名称,除非您将外部名称显式设置为 _。

例如执行闭包:

fund doSomethingInteresting(withFunction: FancyFunction) 
    withFunction(f1: 1.0, f2: 3.0)


根据经验:即使您不喜欢它们,您也应该尝试至少在两个参数具有相同类型时继续使用命名参数,以消除它们的歧义。我还认为最好至少命名所有 IntBoolean 参数。

【讨论】:

我添加了互操作性说明和init 方法的规则。 很棒的解释。谢谢! 但是 multiply1(f1: Double) 和 multiply1(f1param: Double) 有什么区别呢?显然参数的名称是不同的,但这有什么关系呢? 还有一个有趣的事情,如果你想在使用方法时显示第一个参数名,你必须这样定义: func method(#firstParameter: type, secondParameter: Type) 这样做,调用fun的时候会出现这样的: self.method(firstParameter: 3, secondParameter: 5) 如果不加#会出现这样的: self.method(3, secondParameter: 5) 但是这种设计背后的基本原理是什么?是不是想防止我们混淆参数的顺序?如果是这样,为什么一个函数没有这样的限制?【参考方案5】:

因为你的“乘法”函数是一个方法,并且和 Objective-c 一样,方法中的参数是名称的一部分。

例如,您可以这样做。

class Calculator 

    func multiply(factor1:Int, factor2:Int) -> Int
        return factor1 * factor2
    

    func multiply(factor1:Int, factor2:Int, factor3:Int) -> Int
        return factor1 * factor2 * factor3
    


这里有两种不同的方法,名称不同,multiply(factor2) 和 multiply(factor2 factor3)。

此规则仅适用于方法,如果您将其声明为类外部的函数,则函数调用不需要参数名称。

【讨论】:

但这仍然不能解释为什么只能省略第一个参数名称?为什么我不能只为所有参数添加参数名称以保持可读性?这似乎是来自旧 Objective C 的老式代码,其中参数是函数名称的一部分。这导致函数的名称很长。很奇怪,如果我们想学习 Swift,我们仍然必须了解 Objective C 的工作原理。 第一个参数被省略,因为 cocoa 中方法的标准名称看起来像:func multiplyFactor1(factor1: Int, factor2:Int) 所以,当你调用方法时,你省略了第一个参数以避免重复它的名称。 myCalculator.multiplyFactor1(2, facto2:2) 比。 myCalculator.multiplyFactor1(factor1: 2, facto2:2)【参考方案6】:

由于您在示例代码中使用了calculator.multiply(),我假设此函数是calculator 对象的方法。

Swift 从 Objective-c 继承了很多东西,这就是其中之一:

在 Objective-c 中你会做(假设):

[calculator multiply:@9834 factor2:@2321];

equivalent in Swift 是:

calculator.multiply(9834, factor2:2321);

【讨论】:

我打算接受这个答案。我查看了文档,他们在那里使用的示例比我的更有意义。 对于没有使用过 Objective C 的人来说很困惑 :) @Kokodoko 对于使用objective-c 5年的人来说更加困惑:P 对于这个页面上给出的所有解释,它仍然没有多大意义....省略第一个参数,因为函数名称已经包含它? (它没有,不在乘法示例中)。另外,如果 Scratch 忽略了第一个参数类型,因为它太混乱了,为什么他们不忽略所有类型呢?这不是让事情变得更加混乱吗?抱歉,在这里吐槽一下。

以上是关于为啥函数调用需要 Swift 中的参数名称?的主要内容,如果未能解决你的问题,请参考以下文章

x86上为啥C语言调用一个函数要先把参数压栈,之后才是返回地址

为啥我在 Swift 中的初始化程序不断收到“调用中的额外参数”

调用函数时为啥形参的值不能传给实参

Swift6-函数

swift函数中参数名称的间距

Swift初见Swift函数