【问题标题】:Cannot assign class instance to its protocol type?无法将类实例分配给其协议类型?
【发布时间】:2014-12-03 17:52:33
【问题描述】:

请参阅下面的自包含示例。编译器在最后一行(标记为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 的返回类型向下转换(因此上面的最后一行代码sn-p 读取为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

【问题讨论】:

    标签: ios generics swift interface compiler-errors


    【解决方案1】:

    变量的显式向下转换:

    let short = SimpleTrain<T>() as ShortType
    

    或返回值:

    return short as ShortType
    

    解决问题。

    更新

    当我回答这个问题时,我想知道自己如何将Train 协议用作返回类型,因为它是类型别名的。

    看看这段代码:

    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 类型的变量,因此定义返回它的函数没有意义。

    我在官方文档中找不到任何提及,但我确定我在某个地方读到过,我只是不记得在哪里。

    结论:我解释您遇到的错误:

    SimpleTrain<T> is not convertible to 'ShortType'
    

    作为协议类型别名的直接结果。

    请注意,这是我的个人观点,因为除了带有和不带有别名的代码 sn-p 测试协议之外,我目前无法证明这一点。

    【讨论】:

    • 感谢安东尼奥。虽然这绝对是一种实用的解决方案(如果不是更好的话,我会使用这个解决方案)我相信在编译器确实可以隐式推断分配的类型适合变量的情况下不需要显式向下转换。跨度>
    • 请阅读更新后的答案——但不确定是否有意义:)
    • 通过个人,确实是非常好的意见和分析。此外,如果我们沿着这些思路继续下去:类型别名协议将替代通用协议(Apple 工程师宣称它们更好)。您的结论意味着它们不能用作类型(常量、变量、方法......)。在 Java 或 C# 中,这相当于只能使用非遗传接口作为类型。如果这种分析是正确的,那么恕我直言,这是 Swift 中的严重遗漏。我很想就这个问题征询 Swift 上值得信赖的人的意见。
    【解决方案2】:

    首先,您想阅读来自 devforums 的canonical thread。您特别想跳过阅读 jckarter 的 cmets。

    现在到已编辑的问题:

    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 中做的所有事情。 Swift 团队(多次)表示,他们非常清楚这些与关联类型相关的问题(以及类型系统中的其他几个相关弱点)。他们特别了解可以处理这些事情的 Scala 类型系统,并且与当前的 Swift 类型系统有很多共同点(尽管根据我的经验,让复杂的路径相关的关联类型在 Scala 中工作也会导致头发撕裂)。

    也就是说,从您的示例中,您计划使用此功能做什么并不完全清楚。有些 Trains 会返回不同的类型作为它们的 shortTrain() 吗?

    我发现这些问题通常在一般情况下会爆发,但在您面前的应用程序的特定情况下往往是可以解决的。在 Swift 中用可以解决所有问题的代码构建真正任意的类型是很困难的,但是当你专注于你真正需要的类型时,它通常会奏效。例如,如果shortTrain() 返回Self,这显然会变得更简单。如果调用者知道所需的结果类型,那么init(shorten:) 可能可以处理它。像shortCarriages() -&gt; [CarriageType] 这样的协议方法可以提供一个很好的桥梁。在您的设计上保持灵活性,其中一个几乎肯定会奏效。

    【讨论】:

    • 感谢您的回答 Rob。它回答了我所有的问题(包括我将来可能在其他地方问过的一些问题,例如 Swift 团队对此有何看法)。不幸的是,我无法提供任何更具体的信息。我提供了人工示例以删除所有与业务相关的信息。但是,给出的示例尽可能接近,并且我声明该产品是一个库,我几乎不能要求用户为许多方法调用显式指定类型。我找到了另一种方法。仍然可以将具有开放类型别名的协议用作类型会很棒
    【解决方案3】:

    使用协议吗?我对类型理论没有很好的理解,但根据 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)
    

    这个输出:

    火车:[1]

    火车:[3]

    我已经对这种技术进行了试验,因为我有抽象层次结构,其中一种类型是共同的,而其他类型不同但不影响函数签名。通过使用“抽象类”,我可以创建一个“任何类型都具有这种通用类型(而其他类型可能不同)”的数组

    【讨论】:

      猜你喜欢
      • 2023-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多