我想将符合协议的类作为函数参数传递。具体来说:我有一个广度/深度优先搜索算法,该算法基于(分别)队列或堆栈数据结构。我希望有一个函数可以接受Queue或Stack类作为参数。可能被包裹在一个枚举中,该枚举隐藏了Queue和Stack对象,从而使宽度优先或深度优先。
我尝试使用带有简单字符串的if
或switch
变体作为“ depth”或“ breadth”,但是在闭包中声明的变量对外部作用域是不可见的,因此我需要重复这两种情况几乎都是整个功能主体。
这是一个最小的,可重复的示例。实际示例更加复杂,但是我遇到了相同的错误,并认为它具有相同的潜在问题。
protocol Container {
associatedtype T
var container: [T] { get }
func push(_ thing: T)
}
public class fakeQueue<T>: Container {
internal var container: [T] = [T]()
public func push(_ thing: T) { container.append(thing) }
}
public class fakeStack<T>: Container {
internal var container: [T] = [T]()
public func push(_ thing: T) { container.insert(thing, at: 0) }
}
func addValue<Algorithm: Container>(someValue: String, algorithm: Algorithm) {
// next line raises: Cannot specialize a non-generic definition
let importantVariable = algorithm<String>()
importantVariable.push("Something important")
}
// The call raises: Argument type 'fakeQueue.Type' does not conform to expected type 'Container'
addValue(someValue: "_", algorithm: fakeQueue) // or fakeStack
我知道我不能使用algorithm<String>()
。在此版本中,它会引发错误:Cannot specialize a non-generic definition.
,但是当我使用fakeStack<String>()
或fakeQueue<String>()
并避免指定algorithm: fakeQueue
时,它将按预期工作。
我只是想避免必须执行两个功能。
答案 0 :(得分:3)
在Swift中传递类型几乎总是一种不好的气味。 Swift不会将元类型像类型一样对待。
此外,您的addValue
永远都不能按照指定的方式写。您不能将Container传入或传出容器,因为Container是通用协议,不能用作类型(例如,在指定函数参数或函数返回类型时)。
您可以创建符合通用协议的通用类,从而保证可以push
到任何此类的实例。但是您不能在一个人的掌控下进一步统一它们,因为它们都是泛型并且可能以不同的方式解决。
说了这么多,我们现在可以证明,我们可能可以对您的想法采取一个非常好的方法。
考虑到您想做的一般事情,我怀疑您是在追求这样的架构:
protocol Pushable : class {
associatedtype T
init(_ t:T)
var contents : [T] {get set}
func push(_ t:T)
}
final class Stack<TT> : Pushable {
init(_ t:TT) { self.contents = [t]}
var contents = [TT]()
func push(_ t:TT) {
self.contents.append(t)
}
}
final class Queue<TT> : Pushable {
init(_ t:TT) { self.contents = [t]}
var contents = [TT]()
func push(_ t:TT) {
self.contents.insert(t, at:0)
}
}
我将您的容器命名为Pushable,只是因为现在可以调用push
是我们的共同点。您会注意到,我已经在Pushable协议中添加了init
;这样我们就有了一种解决Pushable泛型的方法。无论我们使用哪个值初始化Pushable,其类型都将成为其通用参数化类型;目前,该实例进入contents
,并且可以推送其他实例,尽管稍后将说明如何更改。
现在我们可以这样说:
let stack = Stack("howdy")
stack.push("farewell")
let queue = Queue(1)
queue.push(2)
好的,现在让我们回到将任意值推入任意Pushable的愿望。表示的方法是使用Pushable,而不是将其用作传递类型或返回类型,而应将其用作泛型上的 constraint 。我们允许 将通用协议用于以下目的:
func push<TTT,P>(_ what:TTT, to pushable: P)
where P:Pushable, P.T == TTT {
pushable.push(what)
}
但是您无疑会注意到,我仍然没有提供能够创建队列或堆栈的功能。为此,我们确实需要传递一个元类型。啊哈,但是我给了Pushable一个init
的要求!现在我们可以做到:
func createStackOrQueue<TTT,P>(_ what:TTT, type pushableType: P.Type) -> P
where P:Pushable, P.T == TTT {
return P.init(what)
}
let stack = createStackOrQueue("howdy", type:Stack.self)
这与您要尝试的操作不完全相同,但是也许它已经足够接近以使您前进。
如果您真的坚持要传递元类型,请更改init
,使其也采用元类型:
protocol Pushable : class {
associatedtype T
init(_ t:T.Type)
var contents : [T] {get set}
func push(_ t:T)
}
final class Stack<TT> : Pushable {
init(_ t:TT.Type) { self.contents = [TT]()}
var contents = [TT]()
func push(_ t:TT) {
self.contents.append(t)
}
}
final class Queue<TT> : Pushable {
init(_ t:TT.Type) { self.contents = [TT]()}
var contents = [TT]()
func push(_ t:TT) {
self.contents.insert(t, at:0)
}
}
现在,我们可以编写一个通用的工厂函数,使其非常接近您最初使用的函数,其中Pushable(堆栈或队列)和内容类型均表示为元类型:
func createPushable<TTT,P>(_ whatElementType:TTT.Type, type pushableType: P.Type) -> P
where P:Pushable, P.T == TTT {
return P.init(TTT.self)
}
我不能说我赞成这种事情,但至少您可以看到它是如何完成的。
现在,我认为我们可以做出与您最初的构想非常相近的东西,在这里我们说是要堆栈还是要排队的东西以及要放入的东西!准备好了吗?
func createPushable<TTT,P>(type pushableType: P.Type, andPush element:TTT) -> P
where P:Pushable, P.T == TTT {
let result = P.init(type(of:element).self)
result.push(element)
return result
}
这是怎么称呼它:
let stack = createPushable(type:Stack.self, andPush:"howdy")