【问题标题】:Returning constrained generics from functions and methods从函数和方法返回受约束的泛型
【发布时间】:2015-02-08 19:58:27
【问题描述】:

我想创建一个返回符合协议的对象的函数,但该协议使用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 的目的是返回基于 key 参数的 StringInt。编译器抛出的错误有点解释为什么它被禁止,但它确实提供了修复语法的洞察力。

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

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

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

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

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

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

【问题讨论】:

  • 任何人都可以帮助解释为什么在 Swift 3 中以下代码有类似的错误? func paths() -> T? { 返回 [Int]() }

标签: generics swift protocols type-alias


【解决方案1】:

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

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

func returnsSomethingWithAwesomeness&lt;T: HasAwesomeness&gt;(key: String) -&gt; T

是说:returnsSomethingWithAwesomeness 是一个可以对任何类型T 进行操作的函数,只要T 符合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 被替换为 Stringnot 被替换为 HasAwesomenessHasAwesomeness 仅用作约束——即限制 T 可以是哪些可能的类型。

当你这样看时,你会发现else 中的return 42 没有意义——你怎么能从一个返回字符串的函数中返回 42?

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

您无法将其与其他 T 进行比较(不保证它支持 ==)。你不能构造新的(谁知道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 实现,由编译器在您调用它们时自动编写(因此它可以修复@ 987654363@ 为特定类型)。不是一个返回协议的函数,而是两个返回两种不同类型的函数。因此,同样returnsSomethingWithAwesomeness 不能在运行时基于某些动态参数返回StringInt,你不能编写返回数组或连续数组的returnCollectionContainingOne 版本.它只能返回一个T,并且在编译时,无论T 实际是什么,编译器都可以填写。

* 这稍微简化了编译器的实际工作,但它会为这个解释做。

【讨论】:

  • 我有一个非常相似的问题,但即使在阅读了问题和你的答案后,我仍然不知道如何正确解决我的问题。你能看看我的问题吗:stackoverflow.com/questions/40749161/…
  • 解释得很好:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多