"Не удается преобразовать выражение возврата" в 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 результаты (перегрузка будет переименован to compactMap(_:) в будущих версиях).

    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 может иметь разные значения в зависимости от контекста. Вы можете сказать компилятору более точно, какой из них вы собираетесь использовать.

enter image description here


две обычные цели для применения 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]