Ale*_* N. 7 language-design named-parameters swift
在 Swift 中,当你调用一个函数时,你需要标记参数,除非函数的作者明确允许你不要这样做。在语言设计方面是否有原因?我一直认为参数标签是一种允许调用者以任何对他们有意义的方式对参数进行排序的好方法,但这种方式感觉就像很多无意义/无用的样板。
例子:
如果我们这样定义 makeDog 函数:
makeDog(legs: int, name: String)->Dog{}
Run Code Online (Sandbox Code Playgroud)
那么它必须像这样调用:
makeDog(legs: 4, name: "fido")
Run Code Online (Sandbox Code Playgroud)
并且不能这样调用(编译器错误):
makeDog(name: "fido", legs: 4)
Run Code Online (Sandbox Code Playgroud)
写后笔记:即使是命名参数的 StackOverflow 标签描述也说:
命名参数使您能够通过将参数与参数名称而不是参数在参数列表中的位置相关联来为特定参数指定参数。
Mar*_*eIV 30
为了帮助理解您的问题的答案,您首先必须了解,与支持命名参数的其他语言不同,在 Swift 中,您实际上并没有在调用站点看到参数名称。 您所看到的被称为面向外的标签,虽然它们通常与底层参数名称匹配,但这仅仅是巧合/方便。它们仍然是非常独立的事物,具有完全不同的规则集,理解这种差异是回答您的问题的关键。
\n简而言之,面向外的标签是旨在通过帮助其读起来更像句子来增加调用站点的清晰度的单词,就像您不能随机重新排列句子中的单词一样,您也不应该也能够随机地重新排序这些标签。
\n描述上述内容的一组简单规则可以总结如下......
\n外向标签(外部) \n这些标签仅
在调用站点有意义,因此您传递的内容一目了然。因此,它们可能更多地以“语法”方式起作用,而不是作为标识符。与参数名称不同,向外的标签不必是唯一的,也可以通过将方法签名中的标签设置为下划线来完全省略它们(_ ) 来完全省略它们,这意味着“不要使用标签”对于这个论点。
参数名称(内部) \n这些名称仅在函数/方法的内部主体的上下文中
才有意义。因此,它们必须是唯一的,并且应该根据函数的实现来命名,而不是根据外部调用/使用的方式来命名。
例如,考虑一个将两个数字相加的函数。这是它的定义方式......
\nfunc add(_ valueA: Int, to valueB: Int) {\n return valueA + valueB\n}\nRun Code Online (Sandbox Code Playgroud)\n这就是你如何称呼它。
\nlet result = add(30, to: 12) // gives 42\nRun Code Online (Sandbox Code Playgroud)\n上面的valueA和valueB是仅存在于 的实现中的参数名称add。此外,第一个参数省略其标签(通过在其位置使用下划线),第二个参数将其标签定义为to因为这使得调用站点读起来像一个句子... \'Add 30 to 12\'...在呼叫现场清楚地表明发生了什么。
在大多数语言中,没有向外标签的概念。您只需按序号位置指定函数的参数值,通常用逗号分隔。
\n现在,在某些语言(例如 C#)中,除了按顺序指定参数值之外,您还可以/改为通过参数名称指定它们。如果您按名称指定所有参数,那么按照您想要的任何顺序指定它们在逻辑上似乎是有意义的,但不要求使用名称,因此您可以与序数混合和匹配。
\n但如果你能做到这一点,当你按顺序指定一些内容和按名称指定一些内容时会发生什么?现在你被迫将命名参数放在最后,因为如果你不这样做,它如何知道未命名参数的位置?或者更令人困惑的是,如果您在第三个位置指定参数值,但稍后又按名称指定相同的参数,会发生什么情况?这可能会令人困惑。
\n相比之下,Swift 不仅努力使其调用约定更加一致,而且还努力成为一种自我记录语言,重点关注使用点(即调用站点)的清晰度。因此,Swift 添加了外部/向外标签的概念,默认情况下这些标签是必需的,但如果有意义的话,它允许您将它们设为可选。如果您没有为参数显式指定标签,则标签会隐式设置为与参数相同的名称,但同样,它们仍然是非常不同的东西。
\n现在至于为什么这是一件好事,让我们看一些例子。
\n让我们从一种允许您重新排序命名参数的语言开始。
\nvar result = multiplyValues(valA: 20, valB: 5)\nRun Code Online (Sandbox Code Playgroud)\n既然你可以重新排序,你也可以写这个......
\nvar result = multiplyValues(valB: 5, valA: 20)\nRun Code Online (Sandbox Code Playgroud)\n现在在 Swift 中,您在调用站点使用的不是参数名称,它们是帮助了解签名本身的整体上下文的标签。
\n考虑这个稍微复杂的函数定义......
\nfunc multiply(value: Int, by factor: Double, thenAdd addlAmount: Int){}\nRun Code Online (Sandbox Code Playgroud)\n当调用时,它是这样的......
\nlet result = multiply(value: 20, by: 1.5, thenAdd: 5)\nRun Code Online (Sandbox Code Playgroud)\n这不是比另一个例子更清楚了吗?但需要注意的是,“by”不是参数名称,而是一个根据其在调用中的位置而有意义的标签。 实际的底层参数称为“因子”,这就是函数实现体内使用的参数。
\n遵循 Swift 设计指南,人们甚至可以将第一个参数的标签设为可选,现在读起来像这样......
\nlet result = multiply(20, by: 1.5, thenAdd: 5)\nRun Code Online (Sandbox Code Playgroud)\n同样,发生的事情非常清晰,几乎读起来就像一个句子。
\n现在想象一下,如果您不使用这些标签而只使用参数名称。现在,名称在外部环境中很重要。例如,看看这个
\nlet result = multiply(value: 20, addlAmount: 5, factor: 1.5)\nRun Code Online (Sandbox Code Playgroud)\n清晰度开始变得模糊。是先将 20 乘以 1.5 再加上 5,还是先将 25 乘以 1.5?答案是35还是37.5?
\n如果您使用外部名称/标签怎么办?现在情况更糟了!
\nlet result = multiply(by: 1.5, thenAdd: 5, 20)\nRun Code Online (Sandbox Code Playgroud)\n到底他妈发生了什么?
\n当您以这种方式对它们重新排序时,并不清楚您要乘以 1.5,因为如果这是在实体(结构、类等)中,则很容易会误认为您正在乘以 1.5隐含self的 1.5 (即self.multiply(by:)),然后在末尾有一些随机参数 20 ,没有上下文。再说一次,这还不清楚。
当然,你很自然地可能会回答“但是那是带有可选标签的!” 顺序不重要当且仅当全部标签时,顺序才无关紧要。”但现在您正在弄乱规则并破坏单个特定用例的一致性。
\n也许更相关的问题是你实际上得到了什么这个一次性用例现有规则有这么多优点,那么缺点是什么?如果不允许随机订单,您将无法做什么?
\n正是上述对不一致的厌恶导致 Swift 在 Swift 的早期版本中引入了一项重大更改,其中第一个参数的标签不需要,即使您指定了标签也是如此。它隐式地添加了一个_如果你愿意的话。但与其他论点相比,为什么第一个论点应该得到特殊对待呢?答案是……不应该!所有这些都让人们感到困惑,因此他们在未来的版本中对其进行了更改,因为同样,一致性比聪明更重要,甚至比引入突破性更改更重要。
由于标签名称旨在在调用站点提供清晰度,帮助其读起来更像一个句子,因此可以多次使用相同的标签,因为顺序也很重要。
\n然而,参数名称必须是唯一的,因为它们需要在实现中共存。
\n这是一个演示这一点的示例。请注意标签“and”如何使用两次,但参数名称“a”、“b”和“c”必须是唯一的...
\nfunc add(_ a: Int, and b: Int, and c: Int) -> Int {\n return a + b + c\n}\n\nlet result = add(1, and:2, and:3)\n\nprint(result)\nRun Code Online (Sandbox Code Playgroud)\n面向外部的标签应该只在调用站点有意义,并且应该努力完成句子的语法。然而,参数名称应该只在函数实现的上下文中有意义。
\n遵循这些简单的指导原则,您就会非常清楚什么应该放在哪里以及以什么顺序,以及为什么您不能简单地在调用站点重新排序参数是一件好事。
\n好的,您现在了解了标签对于 Swift 语言有多么重要,并且您也了解了为什么顺序很重要。但有时,您只是陷入了设计糟糕的 API 中,这种 API 完全没有任何意义。也许这就是您想要按您希望的任何顺序调用参数的原因。
\n举个例子,假设有一个名为BadMath您不拥有、不喜欢的包,但由于您无法控制的原因,您必须在项目中使用。现在说它定义了一个add像这样的函数......
class BadMath {\n\n func add(_ value: Int, to value2: Int, butNotBeforeFirstMultiplyingItBy valueMultiplier: Int) -> Int {\n return (value * valueMultiplier) + value2\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n(呃……写这个例子连我自己都觉得痛苦!)
\n所以你不得不使用这个糟糕的 API,它显然以错误的顺序调用事物,结果你的代码现在看起来也像垃圾。而且你不喜欢垃圾代码!您喜欢干净的代码,并且您的代码通常非常出色!但是伙计...那个功能...
\n难道你不只是希望你能改变它吗?如果您可以清理该 API 并使其以一种对您而言逻辑上更有意义的方式工作,那不是很棒吗?我的意思是...如果你能做一些事情,比如这样的话,那就太棒了...
\nextension BadMath {\n\n func multiply(_ value: Int, by factor: Int, thenAdd addlValue, Int) -> Int {\n add(value, to: addlValue, butNotBeforeFirstMultiplyingItBy: factor)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n等一下!你可以,而且刚刚就做到了!
\n重点是,如果您正在使用一个您认为确实无法忍受的 API,并且您确实希望按照您所希望的顺序命名和/或调用参数,想要的顺序命名和/或调用参数,那么请疯狂使用您自己的扩展代码库和“修复”他们的 API!
\n我个人一直使用 Swift Packages 甚至 Apple 自己的一些框架来这样做。
\n这一切的目标都是使代码在调用站点更具可读性,因此任何有助于实现该目标的事情总是正确的...咳咳...\'call\' (Heyoooo!我在这里伙计们,整个星期!请给服务员小费! )
\n| 归档时间: |
|
| 查看次数: |
956 次 |
| 最近记录: |