Общий параметр 'T' не может быть выведен после назначения
// Xcode 11.6 / Swift 5
import Foundation
func f<T>(_: (T) -> Void) { }
@discardableResult
func g(_: Int) -> Int { 0 }
f { g($0) } // compiles fine f { let _ = g($0) } // Generic parameter 'T' could not be inferred
В приведенном выше коде универсальная функция f
ожидает в качестве аргумента функцию, которая принимает аргумент типа T
.
Функция g
принимает аргумент типа Int
.
Когда я пишу f { g($0) }
, код компилируется. Я считаю (поправьте меня, если я ошибаюсь) это компилируется, потому что компилятор может сделать вывод, что T
это Int
основано на g
типе аргумента.
Однако, когда я пытаюсь что-то сделать с возвращаемым значением g
, например, в let _ = g($0)
строке, компилятор жалуется, что он больше не может определить тип T
.
Мне кажется, что тип возвращаемого значения не g
должен иметь никакого отношения к тому, как компилятор определяет T
тип, но очевидно, что это так.
Может ли кто-нибудь пролить свет на то, почему это происходит, и что (если вообще) можно сделать, чтобы это исправить?
Ответы
Это может быть или не быть ошибкой компилятора.
Известно, что Swift не пытается вывести типы некоторых замыканий, а именно, многозадачных, как сказано в SR-1570 :
Это правильное поведение: Swift не выводит параметры или возвращаемые типы из тел замыканий с несколькими операторами.
Однако ваше закрытие состоит только из одного утверждения, а точнее одного объявления. Возможно, хотя и странно, что они разработали это так, что Swift не пытается вывести типы, если замыкание также содержит одно объявление . Например, это тоже не компилируется
f { let x: Int = $0 } // nothing to do with "g"! The issue seems to be with declarations
Если бы это было намеренно, объяснение этого могло бы заключаться в том, что одно объявление в закрытии в любом случае не имеет большого смысла. Все, что заявлено, использоваться не будет.
Но опять же, это всего лишь предположение, и это тоже может быть ошибкой.
Чтобы исправить это, просто сделайте это не-декларацией:
f { _ = g($0) } // this, without the "let", is IMO the idiomatic way to ignore the function result
Или же
f { g($0) } // g has @discardableResult anyway, so you don't even need the wildcard
Функция f
принимает функцию как параметр, который, в свою очередь, принимает параметр типа T
и ничего не возвращает ( Void
). Чтобы замыкание позволяло автоматически определять типы, оно должно состоять из одного (а иногда и простого) выражения. Что-либо сложное затрудняет вывод компилятора (что имеет смысл с точки зрения компилятора). По-видимому, let _ = g($0)
для компилятора это сложный оператор. Для получения дополнительной информации см. Обсуждение в этом списке рассылки.
похоже, что @discardableResult дает вам возможность иметь 2 типа функций:
g (_: Int) -> Int и g (_: Int) -> Void (это когда вы не хотите использовать результат функции)
я думаю что
f { g($0) }
- здесь ваш f может вывести тип, потому что он имеет тот же тип
(_: (T) -> Void) and (_: (Int) -> Void)
f { let _ = g($0) }
- в этом случае тип функции g отличается от функции f
(_: (T) -> Void) and (_: (Int) -> Int)
Если вы удалите "let", он снова скомпилируется:
f { _ = g($0) }
Я думаю, что f { let _ = g($0) }
- вернуть только значение Int f { _ = g($0) }
- вернуть функцию (_: (Int) -> Int)
Может быть, это ключ