无法将类实例分配给其协议类型?

问题描述:

请参阅下面的示例。编译器在最后一行报告错误(由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]这样的协议方法可能会提供一个很好的桥梁。保持灵活的设计,其中一个几乎肯定会奏效。

+0

谢谢你的回答Rob。它回答了我所有的问题(包括我未来可能会问到的其他问题,例如Swift团队对此有何看法)。不幸的是,我无法提供任何更具体的信息。我提供了一个人造的例子,以清除所有业务相关的信息。但是,给出的示例尽可能接近,我声明该产品是一个库,我很难要求用户为许多方法调用明确指定类型。我找到了另一种方法。仍然使用打开类型别名作为类型的协议会很好 – drasto 2014-10-09 15:25:02

任一变量的显式垂头丧气:

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' 

请注意,这是我的个人意见,因为我现在无法证明它除了代码片段测试协议有和没有别名。

+0

谢谢你这个安东尼奥。虽然这绝对是实用的解决方案(如果找不到更好的解决方案,我会使用它),但我认为,在编译器确实可以隐式推断分配的类型适合变量的情况下,不应要求显式向下转换。 – drasto 2014-10-09 07:01:28

+0

请阅读更新的答案 - 不知道它是否有意义,但:) – Antonio 2014-10-09 07:53:32

+0

通过个人,这是非常好的意见和分析确实。此外,如果我们继续沿着这些方向发展: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]

我已经使用这种技术用于在那里我有抽象的层次结构,其中一类是共同的,等其他不同,但不影响功能的签名的情况下尝试。通过使用“抽象类”,我可以创建一个“任何类型与这一个通用类型的共同(而其他人可以不同)”