当前位置: 首页 > 面试题库 >

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

艾志尚
2023-03-14
问题内容

我想创建一个函数,该函数返回符合协议的对象,但是协议使用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
    }
}

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

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

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

错误: 协议“ HasAwesomeness”只能用作一般约束,因为它具有“自我”或相关类型要求

可以想像,其目的returnsSomethingWithAwesomeness是返回StringInt基于确定的key参数。编译器抛出kinda-
sorta的错误使我们理解为什么不允许这样做,但它确实提供了修复语法的见解。

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

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

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

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

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

好吧,现在我被困住了。有人可以帮我填补对类型和泛型的理解空白,可能使我找到有用的资源吗?


问题答案:

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

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

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

就是说:returnsSomethingWithAwesomeness是一个可以对任何类型进行操作的函数,T只要T符合即可HasAwesomeness

但是要填写的内容是在调用T该点时确定的returnsSomethingWithAwesomeness
Swift将查看呼叫站点上的所有信息并确定是什么类型T,然后T用该类型替换所有占位符。*

因此,假设在呼叫站点选择的T是a 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
    }
}

请注意,T用代替,String不是 用的类型代替HasAwesomeness
HasAwesomeness仅用作约束–即限制可能的类型T

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

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

您无法将其与其他Ts进行比较(不保证它支持==)。您不能构造新的(谁知道是否T会有合适的initialize方法?)。而且您不能从字符串或整数文字中创建它(这样做必须T符合StringLiteralConvertibleor或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()

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

*这是编译器实际操作的略微简化,但出于解释的目的。



 类似资料:
  • 我正在实现我自己的泛型链表类,它有一个名为的实例方法,该方法生成链表的数组副本并返回它。然而,每当我试图在实例上调用该方法时,我总是得到错误消息“sllist.this cannot be referenced from a static context”。我搜索了一下,有些人说这是因为我没有在实例上调用那个方法,但我确实调用了。 下面是类: 它有一些方法,如,它们都没有问题。 是我不断收到错误消

  • 我正在学习TypeScript,目前正在尝试创建一个通用排序函数。 我有两个接口: 而我现在的函数看起来是这样的: 上面的代码工作,但我得到警告: 没有与此调用匹配的重载。重载5中的1’(值:字符串|数字|日期):日期),导致以下错误。“T[K]”类型的参数不能分配给“string | number | Date”类型的参数。类型“T[keyof T]”不可分配给类型“string | numbe

  • 在过去一周左右的时间里,我一直在为Scala开发一个类型化、索引化的数组特性。我希望将该特征作为类型类提供,并允许库用户以他们喜欢的方式实现它。下面是一个示例,使用列表的列表来实现2D数组类型类: 这一切看起来都很好。我遇到的困难是,我希望将索引类型约束为一个已批准类型的列表(用户可以更改)。由于程序不是所有已批准类型的原始所有者,所以我想用一个typeclass来表示这些已批准类型,并让已批准类

  • 我想允许我的类构造函数接受这个类的一个实例,在这种情况下返回这个相同的实例而不是创建一个新对象。就像所做的那样: 我想我需要为此重写方法,并在方法中处理这个特殊情况。这有什么食谱吗? 当构造函数被赋予一个将返回不变的类实例时,我宁愿完全跳过,但我看不出有什么办法。我还对 没有参数,却神奇地从(词法或动态?)上下文,我决定添加一个“回调”来完成类定义,而不是使用它:

  • 我正在实现我自己的通用链表类,它有一个名为的实例方法,它可以制作链表的数组副本并返回它。但是,每当我尝试在实例上调用该方法时,我都会收到错误消息“SLList.this不能从静态上下文中引用”。我搜索了一下,有些人说这是因为我没有在实例上调用该方法,但我确实调用了。 下面是该类: 它有一些方法,比如<code>addLast、addFirst</code>它们没有问题。 是我不断收到错误消息的行,

  • 泛型的类型约束 swapTwoValues(_:_:)函数和Stack类型可以用于任意类型. 但是, 有时在用于泛型函数的类型和泛型类型上, 强制其遵循特定的类型约束很有用. 类型约束指出一个类型形式参数必须继承自特定类, 或者遵循一个特定的协议、组合协议. 例如, Swift的Dictionary类型在可以用于字典中键的类型上设置了一个限制. 如字典中描述的一样,字典键的类型必须是可哈希的. 也