为什么我会收到“Protocol ...只能用作通用约束,因为它具有Self或关联类型要求”的错误?

4

我按如下方式为 Int 编写了一个扩展。

extension Int {
    func squared () -> Int {
        return self * self
    }
}

print(10.squared()) // works

上述代码有效。现在我想扩展IntegerType协议,以便Int、UInt、Int64等都符合要求。我的代码如下。
extension IntegerType {

    func squared () -> IntegerType { // this line creates error

        return self * self

    }
}

我遇到了错误:
协议“IntegerType”只能用作泛型约束,因为它具有Self或相关类型要求。
我已经看过this问题及其视频和this问题,但仍无法理解。我只知道在这种情况下有一些关联类型,即Self,但无法联系起来。我感觉我的对于Generics主题的了解不足也是一个原因...
有人可以详细讲解一下这个主题以及为什么扩展会创建一个错误吗?

1
还要确保查看这篇Medium文章,它提供了关于这个问题的极好见解。 - mfaani
2个回答

7

你只需要返回 Self

编辑/更新:

注意:在Swift 4中,您可以通过扩展Numeric协议来扩展所有数字类型(整数和浮点数)。

Swift 4

extension Numeric {
    func squared() -> Self {
        return self * self
    }
}

Swift 3

extension Integer {
    func squared() -> Self { 
        return self * self
    }
}

Leo,你能看一下这个评论吗?我认为那个答案有些错误,而你的是正确的。我对吗?你能详细解释一下吗?除了新的Numeric协议之外,Swift4中还有什么新的东西会影响到这个问题吗? - mfaani
@Honey,没有什么需要详细解释的。被接受的答案解释了你为什么会出现错误,这也是你所问的。我只是提供了你问题的解决方案。请注意,由你决定哪一个标记为正确答案。 - Leo Dabus

3

一个函数的返回类型只能是具体的 Type

关键在于 Type任何完全由自身定义的结构体、类或协议都是纯粹的 Type 但是,当协议或结构体依赖于另一个通用类型占位符(如 T)时,则为部分类型。

Type 是编译器必须分配一定内存的数据结构。

因此,像这样的代码:

let a = Array<T>() 或者 let b = T 对于编译器来说不足以在编译时推断出结果。

因此,这是行不通的。

  extension IntegerType {

    func squared () -> IntegerType { // this line creates error

        return self * self

    }
}

在这里,IntegerType 是一个部分类型。它是一个通用协议,只有符合条件的时候我们才能知道确切的类型。类似于 Array。Array 本身不是一种类型,而是一个通用容器。只有当有人使用 Array() 或 Array()... 来创建它时,它才有一个类型。
你也遇到了相同的情况。
public protocol IntegerType : _IntegerType, RandomAccessIndexType {

然而,再次考虑,
public protocol RandomAccessIndexType : BidirectionalIndexType, Strideable, _RandomAccessAmbiguity {
@warn_unused_result
    public func advancedBy(n: Self.Distance) -> Self

再说一遍,
   public protocol _RandomAccessAmbiguity {
    associatedtype Distance : _SignedIntegerType = Int
   }

因此,由于RandomAccessIndexType具有自我要求,这意味着在某人符合它之前,Self是未知的占位符。它是部分类型。
由于IntegerType符合RandomAccessIndexType和需要Distance关联类型的_RandomAccessAmbuiguity。
因此,你也不能这样做。
let a: IntegerType = 12

再次提到IntegerType需要了解Self和Distance(关联类型)的信息。

而Int则提供了如下详细信息:

public struct Int : SignedIntegerType, Comparable, Equatable {
    /// A type that can represent the number of steps between pairs of
    /// values.
    public typealias Distance = Int

因此,你可以这样做。
let a:Int = 10

因为它为SignedIntegerType提供了Self,而为其其他对应类型提供了Distance。

简单来说:

部分类型不能用于可以使用具体类型的地方。部分类型适用于其他泛型和对其进行约束。


下划线是什么意思? - mfaani
1
它们是Swift内部实现协议的一部分。虽然你可以公开访问它们,但我不建议这样做。 - kandelvijaya
我是说_IntegerTypeIntegerType之间有什么区别? - mfaani
任何遵循 _ 的协议都意味着该协议不应公开使用。这是 Swift 标准库实现的一部分。他们会一次又一次地修订这个内部协议,以使其符合高层视图。如果您公开使用它,尽管 Xcode 不会建议,但当内部发生变化时,您可能需要重新做。 - kandelvijaya
已经过去一年多了。我慢慢开始在我的代码中使用泛型,并有了更好的理解。两个问题:
  1. 在编译时并不足以提供给编译器推断的信息。
我不明白。我的意思是,通常情况下,泛型参数的类型在其所有函数中本来就是未知的。为什么你写的扩展程序不可以是同样的情况呢?这有什么不同吗?
  1. 此外,你说:“函数返回类型只能是具体类型”。但是许多函数返回“T”。这不是和你刚才说的相矛盾吗?
- mfaani
@Honey:1. 泛型参数的类型未知是不正确的说法。编译器会根据调用者指定的类型来解析泛型参数的类型。2. 一些协议确实声明了返回 T 的函数,但这些协议总是被定义 T 的类型所采用。它们通过具体地声明 T 是某个具体类型或声明自己的泛型参数,将指定具体类型的责任放到 它们 的调用者身上。无论哪种方式,T 都会与一个具体类型相关联。如果没有这样做,编译器就会禁止它。 - erikprice

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接