从函数和方法中返回约束泛型

时间:2014-12-10 14:58:22

标签: generics swift protocols type-alias

我想创建一个返回符合协议的对象的函数,但该协议使用typealias。鉴于以下玩具示例:

protocol HasAwesomeness {
    typealias ReturnType
    func hasAwesomeness() -> ReturnType
}

extension String: HasAwesomeness {
    func hasAwesomeness() -> String {
        return "Sure Does!"
    }
}

extension Int: HasAwesomeness {
    func hasAwesomeness() -> Bool {
        return false
    }
}

StringInt已扩展为符合HasAwesomeness,并且每个都实现了hasAwesomeness()方法以返回其他类型。

现在我想创建一个返回符合HasAwesomeness协议的对象的类。我不关心课程是什么,只是我可以发送信息hasAwesomenss()。当我尝试以下操作时,生成编译错误:

class AmazingClass: NSObject {
    func returnsSomethingWithAwesomeness(key: String) -> HasAwesomeness {
        ...
    }
}
  

错误:协议' HasAwesomeness'只能用作通用约束,因为它具有Self或关联类型要求

您可以想象,returnsSomethingWithAwesomeness的意图是返回基于StringInt的{​​{1}}参数。编译器抛出有点错误的错误是有道理的,为什么它被禁止,但它确实提供了修复语法的洞察力。

key

好吧,我的阅读方法func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T { ... } 是一种泛型方法,它返回具有子类型returnsSomethingWithAwesomeness的任何类型T。但是,以下实现会引发更多编译时类型错误:

HasAwesomness
  

错误:键入&#39; T&#39;不符合协议&#39; StringLiteralConvertible&#39;

     

错误:键入&#39; T&#39;不符合协议&#39; IntegerLiteralConvertible&#39;

好吧,好吧,现在我被卡住了。有人请帮助填补我对类型和泛型的理解上的空白,可能指向我有用的资源吗?

1 个答案:

答案 0 :(得分:59)

我认为理解这里发生了什么的关键是区分在运行时动态确定的事物和在编译时静态确定的事物。在大多数语言(如Java)中,协议(或接口)都是在运行时获取多态行为,而在Swift中,具有相关类型的协议也用于获取多态行为,这无济于事在编译时

每当您看到通用占位符(例如示例中的T)时,在编译时确定为此T填写的类型。所以,在你的例子中:

func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T

说:只要returnsSomethingWithAwesomeness符合TT就可以对任何类型的HasAwesomeness进行操作。

T填写的内容是在调用returnsSomethingWithAwesomeness时确定的 - Swift将查看调用网站上的所有信息并确定T的类型,以及用该类型替换所有T占位符。*

因此,假设在调用网站上选择的是TString,您可以将returnsSomethingWithAwesomeness视为在所有占位符T被替换时重写与String

// giving the type of s here fixes T as a String
let s: String = returnsSomethingWithAwesomeness("bar")

func returnsSomethingWithAwesomeness(key: String) -> String {
    if key == "foo" {
        return "Amazing Foo"
    }
    else {
        return 42
    }
}

注意,T替换为String不是,类型为HasAwesomenessHasAwesomeness仅用作约束 - 即限制T可能的类型。

当你这样看时,你可以看到return 42中的else毫无意义 - 你如何从一个返回字符串的函数中返回42?

为了确保returnsSomethingWithAwesomeness可以使用最终的T,Swift会限制您仅使用那些保证可以从给定约束中获得的函数。在这种情况下,我们对T的了解是它符合HasAwesomeness。这意味着您可以在任何returnsSomethingWithAwesomeness上调用T方法,或将其与另一个将类型约束为HasAwesomeness的函数一起使用,或者将一个类型为T的变量指定给另一个一个(所有类型支持分配),就是

您无法将其与其他Ts进行比较(不保证它支持==)。你不能构造新的(谁知道T是否有适当的初始化方法?)。并且您无法从字符串或整数文字创建它(这样做需要T符合StringLiteralConvertibleIntegerLiteralConvertible,这不一定 - 因此这两个错误当你尝试使用这些文字之一创建类型时。)

可以编写返回所有符合协议的泛型类型的泛型函数。但是返回的将是特定类型,而不是协议,因此不会动态确定哪种类型。例如:

func returnCollectionContainingOne<C: ExtensibleCollectionType where C.Generator.Element == Int>() -> C {

    // this is allowed because the ExtensibleCollectionType procol 
    // requires the type implement an init() that takes no parameters
    var result = C()

    // and it also defines an `append` function that allows you to do this:
    result.append(1)

    // note, the reason it was possible to give a "1" as the argument to
    // append was because of the "where C.Generator.Element == Int" part
    // of the generic placeholder constraint 

    return result
}

// now you can use returnCollectionContainingOne with arrays:
let a: [Int] = returnCollectionContainingOne()

// or with ContiguousArrays:
let b: ContiguousArray = returnCollectionContainingOne()

将此代码中的returnCollectionContainingOne视为真正的两个函数,一个为ContiguousArray实现,一个用于Array,由编译器在您调用它们时自动编写(和因此它可以将C固定为特定类型。不是一个返回协议的函数,而是两个返回两个不同类型的函数。因此,基于某些动态参数,returnsSomethingWithAwesomeness在运行时无法返回StringInt,您无法编写returnCollectionContainingOne的版本T返回一个数组或一个连续的数组。它可以返回的全部是T,并且在编译时,编译器可以填写实际上{{1}}的任何内容。

*这是对编译器实际执行的操作的略微过度简化,但它会为此解释。