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
.