无法将类实例分配给其协议类型?
请参阅下面的示例。编译器在最后一行报告错误(由COMPILE ERROR
标记),我将SimpleTrain
的一个实例分配给它(按我的最佳判断)符合的协议类型。我怎样才能编译它?我究竟做错了什么?或者是这个编译器问题?无法将类实例分配给其协议类型?
protocol Train {
typealias CarriageType
func addCarriage(carriage: CarriageType)
func shortTrain<ShortType: Train where ShortType.CarriageType == CarriageType>() -> ShortType
}
class SimpleTrain<T> : Train {
typealias CarriageType = T
private var carriages: [T] = [T]()
func addCarriage(carriage: T) {
carriages.append(carriage)
}
func shortTrain<ShortType: Train where ShortType.CarriageType == CarriageType>() -> ShortType {
let short = SimpleTrain<T>()
short.addCarriage(carriages[0])
return short //COMPILE ERROR: SimpleTrain<T> is not convertible to 'ShortType'
}
}
编辑:即使我明确垂头丧气上述shortTrain
的返回类型(所以上面的代码片段的最后一行读取return short as ShortType
)为suggested by Antonio仍有调用函数shortTrain
时编译错误:
let s = SimpleTrain<String>()
s.addCarriage("Carriage 1")
s.addCarriage("Carriage 2")
let a = s.shortTrain() //ERROR: Cannot convert the expression's type '()' to type 'Train'
let b = s.shortTrain<SimpleTrain<String>>() //ERROR: cannot explicitly specialize a generic function
首先,您想从devforums上阅读canonical thread。你特别想跳过去阅读jckarter的评论。
我们编辑的问题:
let a = s.shortTrain() //ERROR: Cannot convert the expression's type '()' to type 'Train'
这是因为你没有给编译器足够的信息来确定的a
类型。通过它看到的东西想:
func shortTrain<ShortType: Train where ShortType.CarriageType == CarriageType>() -> ShortType {
let a = s.shortTrain()
编译器需要在编译时找出的a
的类型,它不能处理抽象类型。它需要一个完全指定的类型ShortType
(所有指定的泛型,所有的类型别名已解决)。它环顾四周,它在ShortType
上看到了一些限制,但它没有看到任何实际给出的类型。它只有a
,这并没有提供任何提示。
因此,不幸的是,你不得不明确告诉它你想要发生什么。
let a: SimpleTrain<String> = s.shortTrain()
这可能是你要什么的对面,但它是所有你可以做的雨燕现在。 Swift团队多次表示,他们很清楚这些问题与相关类型(以及类型系统中的其他几个相关弱点)。他们特别注意到可以处理这些事情的Scala类型系统,并且与当前的Swift类型系统有很多共同之处(尽管根据我的经验,在Scala中获取复杂的路径依赖关联类型也会导致头发撕裂)。
也就是说,从你的例子中可以看出你计划用这个功能做什么。有些列车会返回一个不同的类型作为他们的shortTrain()吗?
我发现这些问题经常在一般情况下爆炸,但往往在您面前的应用程序的特定情况下是相当可解的。很难在代码中构建真正的任意类型的代码来解决所有问题,但是当你专注于你真正需要的类型时,经常会遇到这种情况。例如,如果shortTrain()
返回Self
,这显然变得更简单。如果主叫方知道所需的结果类型,那么init(shorten:)
可能可以处理它。像shortCarriages() -> [CarriageType]
这样的协议方法可能会提供一个很好的桥梁。保持灵活的设计,其中一个几乎肯定会奏效。
任一变量的显式垂头丧气:
let short = SimpleTrain<T>() as ShortType
或返回值:
return short as ShortType
解决了这个问题。
更新
当我回答这个问题,我不知道自己该Train
协议如何被用作返回类型,是它typealiased。
看看这段代码:
protocol ProtocolWithNoAlias {
}
protocol ProtocolWithAlias {
typealias AliasType
}
var x: ProtocolWithNoAlias?
var y: ProtocolWithAlias?
最后一行报告为编译错误:
Protocol 'ProtocolWithAlias' can only be used as a generic constraint because it has Self os associated type requirements
这意味着你不能使用ProtocolWithAlias
作为具体类型,这反过来又意味着你不能声明具有ProtocolWithAlias
类型的变量,因此定义返回它的函数是没有意义的。
我在官方文档中找不到任何提及,但我确信我在某处阅读,我只是不记得在哪里。
结论:我理解您遇到麻烦的错误:为被typealiased协议的直接后果
SimpleTrain<T> is not convertible to 'ShortType'
。
请注意,这是我的个人意见,因为我现在无法证明它除了代码片段测试协议有和没有别名。
谢谢你这个安东尼奥。虽然这绝对是实用的解决方案(如果找不到更好的解决方案,我会使用它),但我认为,在编译器确实可以隐式推断分配的类型适合变量的情况下,不应要求显式向下转换。 – drasto 2014-10-09 07:01:28
请阅读更新的答案 - 不知道它是否有意义,但:) – Antonio 2014-10-09 07:53:32
通过个人,这是非常好的意见和分析确实。此外,如果我们继续沿着这些方向发展:Typealiased协议将取代通用协议(Apple工程师声明他们更好*)。你的结论意味着它们不能用作类型(常量,变量,方法......)。在Java或C#中,相当于只能使用非遗传接口作为类型。如果这种分析是正确的,这是恕我直言严重遗漏Swift。我会非常感兴趣的是,在Swift上获得关于这个问题的可信度的意见。 – drasto 2014-10-09 08:28:03
您是否有要使用协议?我对类型理论的掌握并不是很好,但是根据Apple线程和上面的讨论,问题似乎又回到了“更高级别的类型”这个话题。
也就是说,如果你可以尝试使用一个抽象基类来近似(与那就是它实际上不是抽象的警告,以及结构不工作),请按以下形式:
class AnyTrain<CarriageType> {
func addCarriage(carriage: CarriageType) {}
func shortTrain() -> AnyTrain<CarriageType> { return AnyTrain<CarriageType>() }
}
class SimpleTrain<T> : AnyTrain<T>, Printable {
private var carriages: [T] = [T]()
override func addCarriage(carriage: T) {
carriages.append(carriage)
}
override func shortTrain() -> AnyTrain<T> {
let short = SimpleTrain<T>()
short.addCarriage(carriages[0])
return short //COMPILE ERROR: SimpleTrain<T> is not convertible to 'ShortType'
}
var description:String {
return "Train: \(carriages)"
}
}
let train = SimpleTrain<Int>()
train.addCarriage(1)
train.addCarriage(2)
train.addCarriage(3)
let shortTrain = train.shortTrain()
println(shortTrain)
// obviously you can refer to it as the base
let anotherTrain:AnyTrain<Int> = SimpleTrain<Int>()
anotherTrain.addCarriage(3)
anotherTrain.addCarriage(2)
anotherTrain.addCarriage(1)
let anotherShortTrain = anotherTrain.shortTrain()
println(anotherShortTrain)
这输出:
Train: [1]
Train: [3]
我已经使用这种技术用于在那里我有抽象的层次结构,其中一类是共同的,等其他不同,但不影响功能的签名的情况下尝试。通过使用“抽象类”,我可以创建一个“任何类型与这一个通用类型的共同(而其他人可以不同)”
谢谢你的回答Rob。它回答了我所有的问题(包括我未来可能会问到的其他问题,例如Swift团队对此有何看法)。不幸的是,我无法提供任何更具体的信息。我提供了一个人造的例子,以清除所有业务相关的信息。但是,给出的示例尽可能接近,我声明该产品是一个库,我很难要求用户为许多方法调用明确指定类型。我找到了另一种方法。仍然使用打开类型别名作为类型的协议会很好 – drasto 2014-10-09 15:25:02