"Не удается преобразовать выражение возврата" в flatMap с бессмысленным выражением внутри
осмотра .lazy
для функций высокого порядка и получили некоторые интересные ошибки компиляции, связанные с функцией flatMap (и, возможно, другие)
примеры
let array = [1, 2, 3, 4, 5, 6] array .flatMap { print("DD") return // Cannot convert return expression of type 'Int' to return type 'String?' } .forEach { print("SS") print() }
комментируя немного
array .flatMap { // print("DD") return } .forEach { print("SS") print() }
и все работает.. еще более интересный пример
array .flatMap { let z = return // Or return z - all is the same "Cannot convert return expression of type 'Int' to return type 'String?'" } .forEach { print("SS") print() }
что может вызвать такое поведение?
2 ответов
на flatMap(_:)
метод on Sequence
в настоящее время (по состоянию на Swift 4) имеет два разных значения:
-
он может принять закрытие преобразования, которое возвращает необязательный
T?
, и он вернет[T]
, отфильтровываяnil
результаты (перегрузка будет переименован tocompactMap(_:)
в будущих версиях).public func flatMap<ElementOfResult>( _ transform: (Element) throws -> ElementOfResult? ) rethrows -> [ElementOfResult]
-
он может принять закрытие преобразования, которое возвращает
Sequence
, и он вернется массив, содержащий конкатенацию всех результирующих последовательностей.public func flatMap<SegmentOfResult : Sequence>( _ transform: (Element) throws -> SegmentOfResult ) rethrows -> [SegmentOfResult.Element]
теперь, в Swift 4,String
стал RangeReplaceableCollection
(и, следовательно, a Sequence
). Так что Swift 3 код, который сделал это:
// returns ["foo"], as using the `nil` filtering flatMap, the elements in the closure
// are implicitly promoted to optional strings.
["foo"].flatMap { }
вот это:
// returns ["f", "o", "o"], a [Character], as using the Sequence concatenation flatMap,
// as String is now a Sequence (compiler favours this overload as it avoids the implicit
// conversion from String to String?)
["foo"].flatMap { }
для сохранения совместимости источников, специализированные flatMap
перегрузок добавил для строк:
//===----------------------------------------------------------------------===// // The following overloads of flatMap are carefully crafted to allow the code // like the following: // ["hello"].flatMap { } // return an array of strings without any type context in Swift 3 mode, at the // same time allowing the following code snippet to compile: // [0, 1].flatMap { x in // if String(x) == "foo" { return "bar" } else { return nil } // } // Note that the second overload is declared on a more specific protocol. // See: test/stdlib/StringFlatMap.swift for tests. extension Sequence { @_inlineable // FIXME(sil-serialize-all) @available(swift, obsoleted: 4) public func flatMap( _ transform: (Element) throws -> String ) rethrows -> [String] { return try map(transform) } } extension Collection { @_inlineable // FIXME(sil-serialize-all) public func flatMap( _ transform: (Element) throws -> String? ) rethrows -> [String] { return try _flatMap(transform) } }
таким образом, использование все равно верну [String]
в режиме совместимости Swift 3, но [Character]
в Swift 4.
так почему же
let array = [1, 2, 3, 4, 5, 6]
array
.flatMap {
print("DD")
return // Cannot convert return expression of type 'Int' to return type 'String?'
}
.forEach {
print("SS")
print()
}
сказать вам, что закрытие должно вернуть String?
?
Ну, Swift в настоящее время не выводит параметры и возвращаемые типы для закрытия нескольких операторов (см. это Q & A для получения дополнительной информации). Так что flatMap(_:)
перегружает, когда закрытие возвращает либо общий T?
или универсальный S : Sequence
не имеют права называться без явных аннотаций типа, поскольку они потребуют вывода типа для удовлетворения общих заполнителей.
таким образом, единственная перегрузка, которая имеет право, является специальной String
source compatibility one, поэтому компилятор ожидает, что закрытие вернет String?
.
чтобы исправить это, вы можете явно аннотировать возвращаемый тип закрытия:
array
.flatMap { i -> Int? in
print("DD")
return i
}
.forEach {
print("SS")
print()
}
но если вы на самом деле не используете дополнительную функциональность фильтрации этого flatMap(_:)
перегрузка в вашем реальном коде, вы должны использовать .
flatMap
может иметь разные значения в зависимости от контекста. Вы можете сказать компилятору более точно, какой из них вы собираетесь использовать.
две обычные цели для применения flatMap
массиву, который компилятор может вывести, является
-
развернуть вложенные массивы
let array = [[1, 2, 3, 4, 5, 6], [7, 8, 9]] let flattened = array.flatMap{} print(flattened) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
-
сопоставить с другим типом и фильтровать optionals
let array = ["1", "a", "2", "3", "b", "4", "5", "6"] let flattened = array.flatMap{ Int() } print(flattened) // [1, 2, 3, 4, 5, 6]