Общий параметр 'T' не может быть выведен после назначения

Aug 18 2020
// 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тип, но очевидно, что это так.

Может ли кто-нибудь пролить свет на то, почему это происходит, и что (если вообще) можно сделать, чтобы это исправить?

Ответы

2 Sweeper Aug 18 2020 at 11:45

Это может быть или не быть ошибкой компилятора.

Известно, что 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
2 Malik Aug 18 2020 at 11:44

Функция fпринимает функцию как параметр, который, в свою очередь, принимает параметр типа Tи ничего не возвращает ( Void). Чтобы замыкание позволяло автоматически определять типы, оно должно состоять из одного (а иногда и простого) выражения. Что-либо сложное затрудняет вывод компилятора (что имеет смысл с точки зрения компилятора). По-видимому, let _ = g($0)для компилятора это сложный оператор. Для получения дополнительной информации см. Обсуждение в этом списке рассылки.

AlexanderNikolaychuk Aug 18 2020 at 11:44

похоже, что @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)

Может быть, это ключ