问题 为什么在已定义所有类型时需要通用?


演习是写我自己的 map() 功能结束 Collection (不使用任何功能原语,如 reduce())。它应该处理这样的情况:

func square(_ input: Int) -> Int {
            return input * input
        }
let result = input.accumulate(square) // [1,2,3] =>  [1,4,9]

我的第一次尝试是:

extension Collection {
    func accumulate(_ transform: (Element) -> Element) -> [Element] {

        var array: [Element] = []
        for element in self {
            array.append(transform(element))
        }
        return array
    }
}    

这在游乐场中运行良好,但无法针对测试构建,给出错误:

Value of type '[Int]' has no member 'accumulate'

解决方案是将通用化 accumulate 方法:

extension Collection {
    func accumulate<T>(_ transform: (Element) -> T) -> [T] {

        var array: [T] = []
        for element in self {
            array.append(transform(element))
        }
        return array
    }
}    

我认识到通用版本限制较少(不要求转换返回相同的类型),但鉴于测试不需要这种通用性,为什么编译器?

出于好奇我试过:

extension Collection {
    func accumulate<Element>(_ transform: (Element) -> Element) -> [Element] {

        var array: [Element] = []
        for element in self {
            array.append(transform(element))
        }
        return array
    }
}    

这会引发迷人的构建错误: '(Self.Element) -> Element' is not convertible to '(Element) -> Element' 在 append() 声明。

所以编译器(当然)知道第一个Element是Self.Element,但不会将其他Element类型视为相同。为什么?


更新:

根据答案,似乎拒绝第一个版本是一个编译器错误,修复在XCode 9.2(我在9.1)。

但我仍然想知道是否在

func accumulate(_ transform: (Element) -> Element) -> [Element]

它会看到两种类型(Self.Element 和 Element)或者认识到它们是一样的。

所以我做了这个测试:

let arr = [1,2,3]
arr.accumulate {
    return String(describing: $0)
}

果然,得到了预期的错误: error: cannot convert value of type 'String' to closure result type 'Int'

所以正确的答案是: 只要不存在重载名称的泛型类型,编译器就会将对Element的引用视为相同。

奇怪的是,这成功了:

[1,2,3].accumulate {
    return String(describing: $0)
}

PS。感谢大家的投入!赏金是自动授予的。


10029
2017-12-06 01:48


起源

您的第一次尝试和第二次尝试都会编译并运行没有问题。 - Martin R


答案:


原始构建错误是编译器错误。实际上是编译器  认识到所有的实例 Element 同样的,长久以来 Element 在函数上没有作为泛型类型重载。


8
2017-12-09 20:17





  • 关于第一个问题,使用Xcode 9.2和Swift 4我没有收到任何构建错误,例如:

    '[Int]'类型的值没有成员'累积'

    这样做:

    var mystuff:[Int] = [1,2,3]
    let result = mystuff.accumulate(square)
    

    它只是给了我正确的结果[1,4,9]

  • 对于第二个问题,函数原型是错误的,你应该试试 Self.Element

    extension Collection {
      func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element] {
        var array: [Element] = []
        for element in self {
          array.append(transform(element))
        }
        return array
       }
    }
    

4
2017-12-08 06:19



是的,但您建议的方法相当于问题中的第二次尝试。 - Martin R
@MartinR好吧,显然它不等于编译器。第二次尝试是: (Element) -> Element -> Element。由于在编译时没有正确推断出这种类型,我建议明确使用 (Self.Element) -> Element。 - Andrea Mugnaini


我将专注于你的第二个问题。

func accumulate<Element>(_ transform: (Element) -> Element) -> [Element]

写这个签名的麻烦是你有两个不同类型的同名。

  • 第一种是 Element 这是您的通用类型(尖括号之间的位)。
  • 第二种是 Self.Element,即协议本身声明的集合中元素的类型。通常你不必明确写出 Self. 部分,但因为你的泛型类型与这个类型具有相同的名称,否则Swift无法区分它们。

如果更改泛型类型的名称,差异会更明显:

func accumulate<E>(_ transform: (E) -> E) -> [E]

这相当于 accumulate<Element> 版本 - 更改名称只是突出显示实际发生的事情。

从更一般的意义上讲,Swift会让你根据自己的意愿命名你的类型。但是如果类型的名称与来自不同范围的另一种类型冲突,那么您要么必须消除它的歧义。如果你不消除歧义,Swift将选择最本地的匹配。在您的函数中,泛型类型是“最本地的”。

想象一下,你要定义自己的String类型:

struct String {
  // ...
}

这是完全有效的,但是如果你想使用Swift标准库提供的String类型,你必须这样消除歧义:

let my_string: String = String()
let swift_string: Swift.String = ""

这就是Andrea改变功能签名的原因。你需要告诉编译器 哪一个 你指的是“元素”类型。

func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element]

一般来说,我建议不要使用泛型类型匹配您正在使用的其他类型的名称。这让每个人都感到困惑。


1
2017-12-08 20:01





我对你的第一个问题不太确定。如果它在操场上工作,但不在你的测试中,我的第一个猜测是将该功能公之于众。测试通常在单独的模块中定义,并且为了在另一个模块中显示某些内容,它应该被声明为public。

extension Collection {
  public func accumulate //...
}

1
2017-12-08 20:04



这不是一个访问问题。测试可以访问 internal 只要他们设置正确。 - vortek


实现对a的扩展时 Collection,默认情况下,该集合的关联类型 Element;这将是 扑朔迷离 将您的通用名称命名为“Element”(func accumulate<Element>但是,对于您的情况,甚至不需要声明您的方法签名如下:

func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element]

相反,它应该是:

func accumulate(_ transform: (Element) -> Element) -> [Element]

,如果你的目标是让你的方法只对整数起作用,你应该这样做 约束 您的扩展名仅适用于 BinaryInteger, 如下:

extension Collection where Element: BinaryInteger {
    func accumulate(_ transform: (Element) -> Element) -> [Element] {

        var array: [Element] = []
        for element in self {
            array.append(transform(element))
        }
        return array
    }
}

或者为了扩大范围,它可能是 数字 代替。


1
2017-12-14 10:27