可以抛出和不可抛出的方法中的Swift类型推断

时间:2018-01-22 17:01:40

标签: swift overloading

如您所知,Swift可以根据使用情况推断类型。例如,只要编译器能够推断类型,您就可以使重载方法仅在返回类型上有所不同并自由使用它们。例如,借助附加的显式类型变量,它将保存此方法的返回值。

我发现了一些有趣的时刻。想象一下这个课程:

class MyClass {
    enum MyError: Error {
        case notImplemented
        case someException
    }

    func fun1() throws -> Any {
        throw MyError.notImplemented
    }

    func fun1() -> Int {
        return 1
    }

    func fun2() throws -> Any {
        throw MyError.notImplemented
    }

    func fun2() throws -> Int {
        if false {
            throw MyError.someException
        } else {
            return 2
        }
    }
}

当然,它会像:

let myClass = MyClass()
// let resul1 = myClass.fun1() // error: ambiguous use of 'fun1()'
let result1: Int = myClass.fun1() // OK

但接下来你可以写下这样的话:

// print(myClass.fun1()) // error: call can throw but is not marked with 'try'
// BUT
print(try? myClass.fun1()) // warning: no calls to throwing functions occur within 'try' expression

所以它看起来像是互斥的陈述。编译器试图选择正确的功能;第一次调用它试图强制从Int转换为Any,但是它试图用第二个做什么?

此外,代码如

if let result2 = try? myClass.fun2() { // No warnings
    print(result2)
}

将没有警告,因此可以假设编译器能够在此选择正确的重载(可能基于事实,其中一个重载实际上没有返回任何内容而只抛出)。

我最后的假设是对的吗?警告fun1()是否符合逻辑?我们是否有一些技巧来欺骗编译器或帮助它进行类型推断?

2 个答案:

答案 0 :(得分:5)

显然你永远不应该写这样的代码。它有太多方法可以咬你,正如你所看到的那样。但是,让我们看看为什么。

首先,try只是Swift中的一个装饰。它不适合编译器。这是给你的。编译器计算出所有类型,然后确定是否需要try。它不使用try来确定类型。你可以在这里看到这个:

class X {
    func x() throws -> X {
        return self
    }
}

let y = try X().x().x()

您只需要try一次,即使链中有多个投掷调用。想象一下,如果你基于throws vs non-throws在x()上创建了重载,这将如何工作。答案是“无关紧要”,因为编译器不关心try

接下来是类型推断与类型强制的问题。这是类型推断:

let resul1 = myClass.fun1() // error: ambiguous use of 'fun1()'
斯威夫特永远不会推断出一种模棱两可的类型。这可能是Any or it could be Int`,所以它放弃了。

这不是类型推断(类型已知):

let result1: Int = myClass.fun1() // OK

这也有一个已知的,明确的类型(注意没有?):

let x : Any = try myClass.fun1()

但这需要类型强制(非常类似于您的打印示例)

let x : Any = try? myClass.fun1() // Expression implicitly coerced from `Int?` to `Any`
                                  // No calls to throwing function occur within 'try' expression

为什么这会调用Int版本? try?返回一个Optional(这是一个Any)。因此,Swift可以选择一个表达式,该表达式返回Int?并将其强制转换为AnyAny?并将其强制转换为Any。 Swift几乎总是喜欢Any的真实类型(并且它非常讨厌Any?)。这是避免代码中Any的众多原因之一。它以奇怪的方式与Optional交互。可以说这应该是一个错误,但是Any是一种松散的类型,很难确定其所有的角落情况。

那么这对print有何影响? print的参数为Any,因此这与let x: Any =...示例类似,而不是像let x =...示例。

在考虑这些事情时要记住一些自动强制:

  • 每个T都可以被轻易地强制为T?
  • 每个T都可以明确强制为Any
  • 每个T?也可以明确强迫任何
    • 任何人都可以被轻易地强迫任何人? (也任何??,任何???,和任何????等)
    • 不限? (任何??,Any ???等)可以明确强制转换为Any
  • 每个非投掷功能都可以简单地强制转换为投掷版本
    • 因此,纯粹在“投掷”上超载是危险的

所以混合抛出/非投掷与Any / Any的转换?转换,并将try?投入混合(将所有内容推广到可选项),您已经创造了一个完美的混乱风暴。

显然你永远不应该写这样的代码。

答案 1 :(得分:3)

Swift编译器总是尝试调用最具体的重载函数,因为有几个重载的实现。

您的问题中显示的行为是预期的,因为Swift中的任何类型都可以表示为Any,因此即使您键入注释结果值为Any,例如let result2: Any = try? myClass.fun1(),编译器实际上将调用fun1的实现返回Int,然后将返回值强制转换为Any,因为这是fun1更具体的重载实现。

您可以让编译器通过将返回值强制转换为Any而不是键入注释来调用返回Any的版本。

let result2 = try? myClass.fun1() as Any //nil, since the function throws an error

如果您将fun1的另一个重载版本添加到您的班级,则可以更好地观察到此行为,例如

func fun1() throws -> String {
    return ""
}

fun1有3个重载版本,输出如下:

let result1: Int = myClass.fun1() // 1
print(try? myClass.fun1()) //error: ambiguous use of 'fun1()'
let result2: Any = try? myClass.fun1() //error: ambiguous use of 'fun1()'
let stringResult2: String? = try? myClass.fun1() // ""

正如您所看到的,在此示例中,即使您添加fun1类型注释,编译器也无法决定使用哪个重载版本的Any,因为版本返回Int并且String都是比返回Any的版本更专业的版本,因此不会调用返回Any的版本,但由于两个专用版本都是正确的,编译器无法确定一个人打电话。