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

ede*_*y05 29 generics protocols type-alias swift

我想创建一个函数,返回符合协议的对象,但协议使用a 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
    }
}
Run Code Online (Sandbox Code Playgroud)

String并且Int已经扩展为符合HasAwesomeness,并且每个都实现了hasAwesomeness()返回不同类型的方法.

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

class AmazingClass: NSObject {
    func returnsSomethingWithAwesomeness(key: String) -> HasAwesomeness {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

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

可以想象,目的returnsSomethingWithAwesomeness是返回一个String或者Int基于ok key参数.编译器抛出的错误有点 - sorta是有道理的,为什么它被禁止,但它确实提供了修复语法的洞察力.

func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

好吧,我的阅读方法returnsSomethingWithAwesomeness是一个泛型方法,它返回任何T具有子类型的类型HasAwesomness.但是,以下实现会引发更多编译时类型错误:

func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
{
    if key == "foo" {
        return "Amazing Foo"
    }
    else {
        return 42
    }
}
Run Code Online (Sandbox Code Playgroud)

错误:类型'T'不符合协议'StringLiteralConvertible'

错误:类型'T'不符合协议'IntegerLiteralConvertible'

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

Air*_*ity 60

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

每当您看到一个通用的占位符时,就像T在您的示例中一样,T在编译时确定填充的类型.所以,在你的例子中:

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

正在说:returnsSomethingWithAwesomeness是一个可以在任何类型上运行的功能,T只要T符合即可HasAwesomeness.

但是,T在这一点上确定的内容被确定returnsSomethingWithAwesomeness- Swift将查看呼叫站点的所有信息并确定类型T,并T用该类型替换所有占位符.*

因此,假设在调用站点,选择是Ta String,您可以认为是 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

当你看它这个样子,你可以看到的是return 42else是没有意义的-你怎么会从返回字符串的函数返回42?

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

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

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

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()
Run Code Online (Sandbox Code Playgroud)

想想returnCollectionContainingOne在这个代码确实是两个功能,一个用于实现ContiguousArray,一个用于Array在你打电话给他们(因此它可以固定点由编译器自动写入C是一个特定的类型).不是一个返回协议的函数,而是两个返回两个不同类型的函数.因此,以同样的方式returnsSomethingWithAwesomeness无法返回a StringInt在运行时基于某个动态参数,您无法编写returnCollectionContainingOne返回数组或连续数组的版本.它只能返回一个T,并且在编译时T,编译器可以填写实际的内容.

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