为什么在已定义所有类型时需要泛型?

时间:2017-12-06 01:48:37

标签: swift generics

练习是在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.ElementElement),或者认识到它们是相同的。

所以我做了这个测试:

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。感谢大家的投入!赏金是自动授予的。

5 个答案:

答案 0 :(得分:8)

原始构建错误是编译器错误。事实上,编译器认识到Element的所有实例都是相同的,因为长Element未作为函数上的泛型类型重载。

答案 1 :(得分:4)

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

      

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

    这样做:

    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
       }
    }
    

答案 2 :(得分:1)

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

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]

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

在实施Collection的扩展时,默认情况下,该集合的关联类型为Element;将通用名称命名为“元素”(func accumulate<Element>)会是令人困惑的,但是,对于您的情况,甚至无需按如下方式声明方法签名:

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

相反,它应该是:

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

,如果您的目标是让您的方法仅对整数起作用,则约束您的扩展名应仅适用于 {的集合{3}} ,如下所示:

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

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

或者,为了扩大范围,可以改为 BinaryInteger