【问题标题】:How is Array() ambiguous when the given argument is a known type?当给定的参数是已知类型时,Array() 是如何模棱两可的?
【发布时间】:2018-08-20 21:30:58
【问题描述】:

我看到了一些对我来说没有意义的东西。这是我的代码...

import Foundation

enum SeparatorPaddingLocation{
    case beforeSeparator
    case afterSeparator
}

struct SeparatorToken{
    let separator          : Character
    let formattedSeparator : String
    let paddingLocation    : SeparatorPaddingLocation
}

extension Array where Element == String {

    func aligned(separatorTokens:[SeparatorToken]) -> [String] {

        // This won't compile!
        let remainingSeparatorTokens = Array(separatorTokens.dropFirst())

        return self
    }
}

即使dropFirst() 返回ArraySlice<SeparatorToken>,'let' 也会导致此编译错误。

没有更多上下文的表达类型是模棱两可的

为什么这是模棱两可的?我错过了什么?

我也尝试过像这样完全限定它,但这也没有用...

let remainingSeparatorTokens:[SeparatorToken] = Array(separatorTokens.dropFirst())

【问题讨论】:

  • 不是从ArraySlice推断出来的吗?
  • 不知道该说什么。这是从我的代码中复制的,我也在运行 Xcode 9.2 (9C40b)。
  • 已更新。我只是将确切的代码原样粘贴在操场上,我确实得到了错误。奇怪!
  • 我发现您的更新代码存在问题。不同之处在于函数位于 Array 扩展中。

标签: arrays swift


【解决方案1】:

不是错误,没有什么神秘或类似错误的事情发生。

不是错误:这正是== 运算符在where 子句中可用的原因:要求Element 的类型为String ,正如here 所解释的那样。在页面中搜索“您还可以编写通用 where 子句要求 Item 为特定类型”。

不像错误:当您在扩展中并调用 Array 初始化程序时,编译器会使用您的 where 子句来指示您想要的 Array 的特化。你的where 子句说String,所以Array<String> 是。当您将 ArraySlice<SeparatorToken> 传递给 Array<String> 初始化程序时,编译器无法读懂您的想法。你是不是弄错了,或者你真的想要Array<ArraySlice<SeparatorToken>>?因此,关于歧义的错误消息。

这是 OP 代码的简化和注释版本,以防有人发现它很有启发性。

class Foo<T> {
    var theFoo: T!

    init() { theFoo = nil }
    init(_ aFoo: T) { theFoo = aFoo }
}

extension Foo where T == Int {
    func bar() {
        // This one fails, because our where clause
        // says this extension applies to Foo<Int>,
        // but now we're trying to assign it to a Foo<String>
        let aFoo: Foo<String> = Foo()

        // This one also fails, for the same reason.
        // It uses the other initializer, but we're
        // still inside the Foo<Int> extension.
        let bFoo: Foo<String> = Foo("bFoo")

        // This one works, because we're expressly
        // overriding the where clause
        let cFoo: Foo<String> = Foo<String>("cFoo")
    }
}

歧义错误是有道理的。我在我的where 子句中告诉编译器让它成为Int 扩展,所以它创建了Foo&lt;Int&gt;s。但后来我尝试将它们分配给Foo&lt;String&gt; 类型的变量。编译器无法判断我是真的想要Foo&lt;String&gt; 还是我犯了一个错误,所以它放弃了。

【讨论】:

  • 但是Array()不是顶级函数吗?如果是这样,为什么它不从您传入的内容中推断其类型?如果你说因为我在一个数组中(通过扩展),那么你是说数组本身也定义了一个名为Array() 的函数吗?这不是很混乱吗?我想是这样。这就是为什么我个人觉得这至少是一个疏忽,如果不是错误的话。
  • 它从您的 where 子句推断类型。这不是因为您在扩展程序中。这是因为您位于带有where...== 的扩展程序中。试试这个let foo: [SeparatorToken] = Array(),你会看到无法将Array&lt;String&gt; 转换为类型[SeparatorToken] 的错误。我并不是说Array() 定义了另一个Array() 函数。我是说== 在您的where 子句中所做的正是文档所说的:要求ElementString 类型。这就是为什么您必须限定您的 Array&lt;SeparatorToken&gt; 以消除歧义。
  • 但我要说的是它如何将我的“where”类型与 Array 函数的元素类型联系起来?换句话说,假设我想编写一个名为 NeatoArray() 的类似函数,它的行为方式与 Array() 相同,推断其类型。你会如何编码?再说一次,我知道 is 这个链接。我是说我不明白它是如何链接的,因为我可以在扩展之外使用 Array() 那么它是如何隐含地拾取它的呢?
  • ...除非它不是一个函数,而是一个初始化器。这将更有意义(并解释连接以及为什么我不能编写 NeatoArray() 函数,但如果 NeatoArray 本身是一个类,则可以编写它。)换句话说,我从内部调用初始化程序扩展名,所以它当然会被推断出来。
  • 它将您的where 类型连接到Array,因为您正在扩展Array。此外,Array 是一个结构。所以Array() 必然是一个初始化器。
【解决方案2】:

在泛型扩展中,类型参数绑定到它们的范围。我不确定这是否真的是一件好事(或者如果它真的是一个错误),但它始终显示出来,所以这里的类型参数隐含地想要是String

一个更简单的例子:

let x = Array()  // error: generic parameter 'Element' could not be inferred

这是有道理的。但是把它放在一个扩展中:

extension Array {
    func f() {
        let x = Array()
    }
}

这样编译,x 的类型为 [Element]

在您的情况下,您已将Element 设置为String,因此对Array.init 的调用想要创建一个[String],它会因为SeparatorToken != String 而变得混乱。您需要显式传递类型参数(如您所见):

let remainingSeparatorTokens = Array<SeparatorToken>(separatorTokens.dropFirst())

再说一次,我真的不认为这是 Swift 中的一个特性,它甚至可能被认为是一个错误。但它非常一致。

【讨论】:

  • 使(烦人!)有意义。也就是说,是否有理由像[Type](ArraySlice&lt;Type&gt;) 那样在初始化程序上使用Array&lt;Type&gt;(ArraySlice&lt;Type&gt;)?后者是我在知道前者之前更改了我要使用的代码。
  • 虽然我喜欢 [Type] 作为一种类型,但我认为它作为隐含的 init 读起来非常尴尬。我相信[T]() 标点符号太多,与系统中的任何其他初始化程序都大不相同。但它们编译成完全相同的东西,所以它是风格的。很像使用() -&gt; Void,我认为它比() -&gt; () Swift 更好,即使它们是相同的。
  • 好像闭包也有类似的问题:stackoverflow.com/questions/38888880/…
  • 原来行为是by design
  • @GreatBigBore 我觉得这是一个设计缺陷,从 Swift 开发人员的角度来说,如果编译器拥有所有必要的信息,它应该能够双向推断类型。也许实施这样的事情会带来巨大的挑战,这就是为什么它还不可用。
【解决方案3】:

我只是在操场上运行你的代码并更新如下,然后错误消失了:

import UIKit
import Foundation

enum SeparatorPaddingLocation{
    case beforeSeparator
    case afterSeparator
}

struct SeparatorToken{
    let separator          : Character
    let formattedSeparator : String
    let paddingLocation    : SeparatorPaddingLocation
}

extension Array where Element == String {

    func aligned(separatorTokens:[SeparatorToken]) -> [String] {

        // I update code here
        let remainingSeparatorTokens = Array<SeparatorToken>(separatorTokens.dropFirst())

        return self
    }
}

根据 swift,当您要使用 Array 进行初始化时,Array 需要类型。

我希望这对你有用。

更多阵列详情请访问:https://developer.apple.com/documentation/swift/array

【讨论】:

  • 这并不完全正确。如果将 let 放在数组扩展之外,则不会出现编译错误。
猜你喜欢
  • 2016-09-19
  • 2020-05-11
  • 2011-09-07
  • 1970-01-01
  • 1970-01-01
  • 2021-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多