【问题标题】:why does guard break type inference?为什么守卫打破类型推断?
【发布时间】:2018-05-31 13:02:32
【问题描述】:

我遇到了标题所描述的问题:guard 语句由于某种原因中断了类型推断。我创建了一个Playground 项目来玩。

以下是一些用于设置的样板代码:

import Foundation

struct Model: Decodable {
    var i: String
}

let jsonString = """
{
    "i": "yes"
}
"""

let jsonData = jsonString.data(using: .utf8)
let theModel = try JSONDecoder().decode(Model.self, from: jsonData!)

struct Response<T> {
    var decodedData: T?
}

enum Result<Value> {
    case success(Value)
    case failure
}

struct Client {
    static let shared = Client()
    private init() {}

    func execute<T: Decodable>(completion: (Response<T>) -> ()) {
        let decodedData = try! JSONDecoder().decode(T.self, from: jsonData!)
        completion(Response(decodedData: decodedData))
    }
}

问题来了:

struct ServicesA {
    func loadSomething(completion: (Result<Model>) -> ()) {
        Client.shared.execute { result in      // error: generic parameter 'T' could not be inferred
            guard let decodedData = result.decodedData else { return }
            completion(Result.success(decodedData))
        }
    }
}

struct ServicesB {
    func loadSomething(completion: (Result<Model>) -> ()) {
        Client.shared.execute { result in
            completion(Result.success(result.decodedData!))
        }
    }
}

ServicesA 中断,而ServicesB 编译。

如您所见,唯一的区别是guard let decodedData = result.decodedData else { return }。它破坏了类型推断,因此Client.shared.execute 函数抱怨T 无法推断。

我想知道为什么会发生这种情况以及处理此问题的最合适方法是什么。

【问题讨论】:

  • guard let decodedData = data else { return } 应该是guard let decodedData = result.decodedData else { return }
  • 是的,这就是我刚刚输入的内容 :)
  • 比较stackoverflow.com/q/42213656/2976878——闭包仅在包含单个语句时才参与类型推断。
  • @PrashantTukadiya 是的,谢谢,当我编辑到 stackoverflow 时,这是一个错字。但我的程序格式正确。刚刚编辑了我的问题。
  • @TonyLin 检查我的答案,它会帮助你

标签: ios swift type-inference swift-playground


【解决方案1】:

TLDR;单行闭包编译不同于多行

长答案:让我们暂时忘记单行闭包。当我们编写接受泛型作为参数类型的泛型函数然后调用该函数时,编译器需要在调用时知道所需的函数类型是什么,即其参数的类型。现在考虑这个参数是一个闭包。再次,编译器需要知道调用时闭包的类型(函数签名)是什么。该信息应该在闭包的签名中可用(即参数和返回类型),编译器不担心(并且正确地)闭包的主体。所以服务 A 的行为完全符合编译器的预期,即闭包签名没有提供任何关于其类型的线索。

现在是单行闭包。 Swift 编译器具有仅针对单行闭包的内置类型推断优化。当您编写单行闭包时,编译器首先会启动其类型推断,并尝试从其唯一的单行 body 中找出闭包,包括其返回类型等。这种类型推断适用于单行闭包(有或没有泛型的上下文)。 这种类型推断是您的服务 B 工作的原因

所以我将把这个问题改写为“为什么类型推断适用于单行闭包?”因为这是 Swift 为单行闭包提供的额外功能。一旦你转向多行闭包(它不是特定的保护语句,如果你也输入 print(“hello world”) 也会发生同样的情况),这种类型推断不再可用。虽然可能有其他类型推断检查可用于多行闭包。

您可以做的只是在闭包签名中提供类型信息 即(结果:响应)

【讨论】:

  • 你的答案比我的措辞更好,但你应该写“陈述”,而不是“行”。 Swift 语句可能会跨行分解。
【解决方案2】:

编译器仅在闭包中有单个语句时推断闭包返回类型。

let x = {
    return 1
}()

                // x == Int(1)

let y = {       // error: unable to infer complex closure return type; add explicit type to disambiguate
    print("hi")
    return 2
}()

https://forums.swift.org/t/problems-with-closure-type-inference/11859/2

【讨论】:

  • 这很有趣,但错误似乎与我的不同。他们似乎在抱怨不同的事情?
  • 你的涉及泛型,我猜编译器首先抱怨这个。
  • 其实这是正确的答案,你能提供任何参考吗?
  • 我认为这是对官方 Swift 论坛的评论。我会尝试挖掘一个链接。
【解决方案3】:

您需要使用 Result 将其类型转换为 result,以便编译器了解 T

struct ServicesA {
    func loadSomething(completion:(Result<Model>) -> ()) {
        Client.shared.execute { (result:Response<Model>) in
            guard let data = result.decodedData else {return}
            completion(Result.success(data))
        }
    }
}

【讨论】:

  • 你能解释一下为什么当我有guard 语句时我需要转换它吗?当我不使用guard 作为我给出的示例时,我不需要强制转换它,即ServicesB 类。
【解决方案4】:

问题是您引用了data 变量,但编译器不知道这是什么类型(因为它是通用的)。试试这个:

struct ServicesA {
    func loadSomething<T:Model>(completion:(Result<T>) -> ()) {
        Client.shared.execute { result:Response<T> in
            guard let decodedData = result.decodedData else { return }
            completion(Result.success(decodedData))
        }
    }
}

【讨论】:

  • 你能解释一下为什么ServicesB如果我不转换类型就不会出现这个问题吗?
  • @TonyLin 因为在ServicesB 中,编译器不需要知道您正在使用的Model 的类型。 Model 的类型将由ServicesB 的用户定义,即在实现中,即调用loadSomething 并分配completion 时。 ServicesA 不同,这里你也需要在实现中定义Model,但是Model 的类型对于ServicesA 是不透明的,因为你正在引用它(守卫)。
  • 为什么引用它(守卫)会丢失类型推断?
  • 因为您无法推断定义在范围之外的类型。在这里,通过使用泛型,您允许在另一个类中定义此类型。基本上,守卫不是“破坏类型推断”,而是ServicesB 不是试图推断类型,这就是它在那里工作的原因。
猜你喜欢
  • 2017-11-27
  • 1970-01-01
  • 2021-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-26
  • 1970-01-01
相关资源
最近更新 更多