Почему вывод типа охранника ломает?
Я столкнулся с проблемой, подобной описанной в заголовке: оператор охраны по какой-то причине нарушает вывод типа. Я создал 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
не может быть выведено
Интересно, почему это произошло, и как лучше всего справиться с этой проблемой.
4 ответа
TLDR; компиляция однострочных закрытий отличается от многострочных
Длинный ответ: Давайте на некоторое время забудем однолинейные закрытия. Когда мы пишем универсальную функцию, принимающую универсальный тип аргумента и затем вызываем эту функцию, компилятору необходимо знать во время вызова, какой тип функции требуется, то есть тип ее аргумента. Теперь рассмотрим этот аргумент как закрытие. Опять же, компилятор должен знать, что это за тип замыкания (сигнатура функции) во время вызова. Эта информация должна быть доступна в сигнатуре замыкания (т. Е. Аргумент и тип возвращаемого значения), компилятор не беспокоится (и правильно делает) о теле замыкания. Таким образом, служба A ведет себя отлично, как и ожидалось от компилятора, т. Е. Подпись замыкания не дает никакой подсказки о ее типе.
Теперь приходит в одной строке закрытия. Компилятор Swift имеет встроенную оптимизацию вывода типов только для однострочных замыканий. Когда вы пишете однострочное закрытие, компилятор сначала запускает его вывод типа и пытается выяснить о замыкании, включая его тип возврата и т. Д., Из его единственной строки тела. Этот тип вывода вступает в силу для однострочных замыканий (с контекстом обобщения или без него). Этот тип вывода является причиной того, что ваш сервис B работает
Поэтому я перефразирую вопрос как "Почему вывод типа работает для замыканий на одну строку?", Потому что это дополнительная функция, предоставляемая Swift только для замыканий на одну строку. Как только вы перейдете к закрытию нескольких строк (это не относится к конкретному оператору защиты, то же самое произойдет, если вы также положите print ("hello world")), этот тип вывода больше не доступен. Хотя для многострочных замыканий могут быть доступны другие проверки вывода типов.
Что вы можете сделать, это просто предоставить информацию о типе в подписи замыкания, т. Е. (Результат: Response
Компилятор выводит типы возвращаемых значений замыкания только в том случае, если в замыкании есть один оператор.
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
Вы должны напечатать его result
с ResultT
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))
}
}
}
Проблема в том, что вы ссылаетесь data
переменная, но компилятор не знает, что это за тип (потому что это Generic). Попробуй это:
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))
}
}
}