Olá Habr!
Meu nome é Alexander Zimin, sou desenvolvedor iOS no Badoo. Esta é uma tradução de um artigo do meu colega Schwib, no qual ele descreveu como era a função flatMap no Swift e por que uma de suas sobrecargas foi renomeada para compactMap. O artigo é útil para entender os processos que ocorrem no
repositório Swift e
sua evolução , bem como para o desenvolvimento geral.

Na programação funcional, há uma definição clara de qual
flatMap função
flatMap . O método
flatMap pega uma lista e uma função de transformação (que para cada transformação espera obter zero ou mais valores), aplica-a a cada elemento da lista e cria uma lista única (nivelada). Esse comportamento é diferente da função de
map simples, que aplica uma transformação a cada valor e espera obter apenas um valor para cada transformação.

Para várias versões, o Swift possui um
map e
flatMap . No entanto, no
Swift 4.1, não é mais possível aplicar o
flatMap a uma sequência de valores e ainda passar um fechamento que retorna um valor opcional. Agora existe um método
compactMap para
compactMap .
No início, pode não ser tão fácil entender a essência da inovação. Se o
flatMap funcionou bem, por que introduzir um método separado? Vamos descobrir.
A biblioteca padrão Swift anterior à versão 4.1 forneceu três implementações de sobrecargas para o
flatMap :
1. Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element], S : Sequence 2. Optional.flatMap<U>(_: (Wrapped) -> U?) -> U? 3. Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
Vamos analisar as três opções e ver o que elas fazem.
Sequence.flatMap <S> (_: (Elemento) -> S) -> [S.Element], em que S: Sequence
A primeira sobrecarga é para sequências nas quais um fechamento pega um elemento dessa sequência e converte em outra sequência.
flatMap todas essas seqüências transformadas na sequência final retornada como resultado. Por exemplo:
let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] let flattened = array.flatMap { $0 }
Este é um ótimo exemplo de como o método
flatMap deve funcionar. Vamos transformar (mapear) cada elemento da lista de fontes e criar uma nova sequência. Graças ao
flatMap resultado final é uma estrutura nivelada de sequências transformadas.
Optional.flatMap <U> (_: (Embrulhado) -> U?) -> U?
A segunda sobrecarga é para tipos opcionais. Se o tipo opcional que você está chamando tiver um valor, o fechamento será chamado com o valor sem o invólucro opcional (valor não empacotado) e você poderá retornar o valor opcional convertido.
let a: Int? = 2 let transformedA = a.flatMap { $0 * 2 }
Sequence.flatMap <U> (_: (Elemento) -> U?) -> [U]
A terceira sobrecarga ajudará você a entender para que
compactMap o
compactMap . Esta versão parece a mesma que a primeira, mas há uma diferença importante. Nesse caso, o fechamento retorna opcional.
flatMap processa, ignorando os valores nulos retornados e inclui todo o restante - no resultado como valores sem um invólucro.
let array = [1, 2, 3, 4, nil, 5, 6, nil, 7] let arrayWithoutNils = array.flatMap { $0 }
Mas, neste caso, a encomenda não é realizada. Portanto, esta versão do
flatMap mais próxima do
map que a definição puramente funcional do
flatMap . E o problema com essa sobrecarga é que você não pode usá-lo corretamente onde o
map funcionaria perfeitamente.
let array = [1, 2, 3, 4, 5, 6] let transformed = array.flatMap { $0 }
Esse uso do
flatMap corresponde à terceira sobrecarga, envolvendo implicitamente o valor convertido em opcional e removendo o wrapper para adicionar ao resultado. A situação se torna especialmente interessante se as conversões de string não forem usadas corretamente.
struct Person { let name: String } let people = [Person(name: “Foo”), Person(name: “Bar”)] let names = array.flatMap { $0.name }
No Swift anterior à versão 4.0, obteríamos uma conversão para
[“Foo”, “Bar”] . Mas, começando na versão 4.0, os valores das cadeias implementam o protocolo Collection. Portanto, nosso uso do
flatMap nesse caso, em vez da terceira sobrecarga, corresponderá ao primeiro e obteremos um resultado "nivelado" dos valores convertidos:
[“F”, “o”, “o”, “B”, “a”, “r”]Ao chamar
flatMap você não receberá um erro, porque isso é permitido. Mas a lógica será quebrada, porque o resultado é do tipo
Array<Character>.Type , não a
Array<String>.Type esperada
Array<String>.Type .
Conclusão
Para evitar o uso indevido do
flatMap , a terceira versão sobrecarregada foi removida da nova versão Swift. E para resolver o mesmo problema (removendo valores nulos) agora você precisa usar um método separado -
compactMap .