【问题标题】:Swift 3 generic function ambiguous returnSwift 3 泛型函数模糊返回
【发布时间】:2017-07-20 15:06:26
【问题描述】:

我需要一个函数来解决类中的不同依赖关系。 但是出现编译错误。 是否可以创建通用函数或 Swift 中有一些编译器约束?

import Foundation

protocol Client: class {
    var description: String { get }
}
final class ImportantPerson : Client {
    var description: String {
        return "Important person"
    }
}

protocol Order: class {
    var description: String { get }
}
final class LastOrder : Order {
    var description: String {
        return "Last order"
    }
}

final class A {

    fileprivate func resolveDependency<T>() -> T {
        return resolve() as T
    }

    private func resolve() -> Client {
        return ImportantPerson()
    }
    private func resolve() -> Order {
        return LastOrder()
    }

}

let a = A()
let client: Client = a.resolveDependency()
let order: Order = a.resolveDependency()

print("Client: \(client.description)")
print("Order: \(order.description)")

编辑:这个问题不是关于 Swift 是否允许创建两个仅在返回类型上有所不同的函数。我知道这是可能的。我认为编译器中存在一些人为约束,但在允许从上下文推断所需类型的基本逻辑中却没有。

【问题讨论】:

  • @DávidPásztor:我看不出 Q&A 如何回答 this 问题。
  • 为什么需要一个函数?它不能工作,因为T 可以是 any 类型。为什么不能直接调用a.resolve(),让编译器选择合适的重载函数?
  • @MartinR,我不想修改 Order 和 Person 类,我需要编译器从上下文推断类型。此代码中除了 Client 和 Order 没有其他类型,为什么编译器不能为这两种情况创建两个不同的函数并使用它们?
  • 您可以调用let client: Client = a.resolve(),编译器确实从上下文中选择匹配函数。

标签: swift generics ambiguous


【解决方案1】:

让我们设身处地为编译器着想。想象一下,这不会导致错误,并且您有一个具有不同输出的签名。

每当您调用 resolveDependency&lt;T&gt;() -&gt; T 时,编译器都会返回一个类型 T,它是符合您的情况的协议的实例。

在您的代码中,您使用符合相同协议的不同实例调用此方法。在那个阶段,编译器对此一无所知。它只知道你已经传递了一个T 的实例,它需要给你一个T 形状的结果

到目前为止没有问题。只要你执行

return resolve() as! T

编译器会混淆。我有一个T,但我不知道我会打电话给哪个resolve()...我只知道我有一个T。我怎么知道这是Order 还是Client

为了防止这种混淆,我们有编译器时错误。至少 Swift 是这样。 (我不知道这在其他语言中是如何工作的)

您需要定义具有不同签名的不同方法并相应地转换您的类型以获得相似的结果

fileprivate func resolveDependency<T>() -> T {
  // check if this is an Order
  resolveForOrder()

  // check if this is a Client
  resolveForClient()
}

private func resolveForOrder() -> Order {
  return LastOrder()
}

private func resolveForClient() -> Client {
  return ImportantPerson()
}

这就像试图让汽车修理工修理航天飞机引擎一样。是的,他们都有引擎,他们都靠燃料运转,但机械师只知道如何修理你的汽车引擎,他不是火箭科学家(!)

【讨论】:

  • 但是编译器能够创建此函数的两个不同实例并在适当的情况下使用它们,不是吗?
  • “我怎么知道这是订单还是客户” - 我为变量设置了一个类型,为什么这还不够?
  • @adnako 正如the Q&A I linked to above 中所说,默认情况下,编译器不会为每个应用的T 创建专门的resolveDependency() 实现——只有一个实现 接受任何给定的T.self 元类型并返回该类型的实例。没有resolve() 的重载可以返回任何T,因此没有有效的重载可以分派到(记住:重载决议发生在编译时)。
【解决方案2】:

这段代码运行良好:

import Foundation

protocol Client: class {
    var description: String { get }
}
final class ImportantPerson : Client {
    var description: String {
        return "Important person"
    }
}

protocol Order: class {
    var description: String { get }
}
final class LastOrder : Order {
    var description: String {
        return "Last order"
    }
}

final class A {

    fileprivate func resolveDependency<T>() -> T {
        if T.self == Client.self {
            return resolve() as Client as! T
        } else {
            return resolve() as Order as! T
        }
    }

    private func resolve() -> Client {
        return ImportantPerson()
    }
    private func resolve() -> Order {
        return LastOrder()
    }

}

let a = A()
let client: Client = a.resolveDependency()
let order: Order = a.resolveDependency()

print("Client: \(client.description)")
print("Order: \(order.description)")

但我相信编译器应该自己解决 if else 子句,这并不像我想象的那么难。 当编译器尝试匹配这样的类型时,编译器中也存在一些错误:

    switch T.self {
    case is Client:
        return resolve() as Client as! T
    default:
        return resolve() as Order as! T
    }

【讨论】:

  • 但是现在resolveDependency() 将在运行时对任何不是ClientLastOrder(或LastOrder 的超类型)的T 崩溃。函数签名实际上是一个谎言;它说它可以返回 any 类型 T,但不能这样做。这就是编译器不会也不应该隐式生成此类代码的原因。
  • 是的,这就是为什么我希望编译器检查他的 AST 使用什么类型调用这个函数的原因。他有这个信息。
  • 不总是——如果函数在另一个(已经编译的)模块中怎么办?
  • fileprivate语句
  • 好的,在你的具体情况下是可行的;但是将行为隐式地绑定到访问控制是不直观的。可能需要一个新的关键字或属性来表达一个始终专门实现其实现的通用函数。我实际上完全赞成这样的改变,但 Swift 团队目前还没有。见bugs.swift.org/browse/SR-3829
猜你喜欢
  • 1970-01-01
  • 2016-11-13
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-31
相关资源
最近更新 更多