Em essência, o fold pega os dados em um formato e os devolve a você em outro. Todos os três métodos – fold, foldLeft e foldRight – fazem a mesma coisa, mas de maneira um pouco diferente. Vou primeiro explicar o conceito que todos os três compartilham e, em seguida, explicar suas diferenças.
Aliás, se você vem do Ruby, como eu, é a mesma ideia que injetar. Por algum motivo, não o utilizava com frequência em Ruby, mas o utilizo o tempo todo com Scala.
Vou começar com um exemplo muito simples; somando uma lista de inteiros com dobra.
val numbers = List(5, 4, 8, 6, 2)
numbers.fold(0) { (z, i) =>
a + i
}
// result = 25
O método de dobra para uma lista leva dois argumentos; o valor inicial e uma função. Essa função também leva dois argumentos; o valor acumulado e o item atual na lista. Então aqui está o que acontece:
No inÃcio da execução, o valor inicial que você passou como primeiro argumento é dado à sua função como primeiro argumento. Como o segundo argumento da função, é dado o primeiro item da lista (no caso de fold, pode ou não ser o primeiro item real da lista, conforme você lerá a seguir).
- A função é então aplicada a seus dois argumentos, neste caso uma adição simples, e retorna o resultado.
- Fold então dá à função o valor de retorno anterior como seu primeiro argumento e o próximo item na lista como seu segundo argumento, e o aplica, retornando o resultado.
- Este processo se repete para cada item da lista e retorna o valor de retorno da função depois que todos os itens na lista foram iterados.
- No entanto, este é um exemplo trivial. Vamos dar uma olhada em algo que é mais útil. Usarei o foldLeft no próximo exemplo e explicarei como ele é diferente do fold mais tarde. Por enquanto, pense nisso da mesma forma que dobre.
Aqui está nossa classe e objeto companheiro com o qual trabalharemos.
class Foo(val name: String, val age: Int, val sex: Symbol)
object Foo {
def apply(name: String, age: Int, sex: Symbol) = new Foo(name, age, sex)
}
Digamos que temos uma lista de instâncias de Foo.
val fooList = Foo("Hugh Jass", 25, 'male) ::
Foo("Biggus Dickus", 43, 'male) ::
Foo("Incontinentia Buttocks", 37, 'female) ::
Nil
E queremos transformá-lo em uma lista de strings no formato de [tÃtulo] [nome], [idade]
val stringList = fooList.foldLeft(List[String]()) { (z, f) =>
val title = f.sex match {
case 'male => "Mr."
case 'female => "Ms."
}
z :+ s"$title ${f.name}, ${f.age}"
}
// stringList(0)
// Mr. Hugh Jass, 25
// stringList(2)
// Ms. Incontinentia Buttocks, 37
Como no primeiro exemplo, temos um inÃcio – neste caso e uma Lista de Strings vazia – e a função de operação. Neste exemplo, determinamos qual tÃtulo é apropriado para o item atual, construÃmos a string desejada e a anexamos ao final do acumulador (que é uma lista).
Agora, a diferença entre fold, foldLeft e foldRight.
A principal diferença é a ordem em que a operação de dobra itera na coleção em questão. foldLeft começa no lado esquerdo – o primeiro item – e itera para a direita; foldRight começa no lado direito – o último item – e itera para a esquerda. a dobra não segue uma ordem especÃfica.
Como a dobra não segue uma ordem especÃfica, há restrições no valor inicial e, portanto, no valor de retorno (em todas as três dobras, o tipo do valor inicial deve ser igual ao valor de retorno).
A primeira restrição é que o valor inicial deve ser um supertipo do objeto que você está dobrando. Em nosso primeiro exemplo, estávamos dobrando em um tipo List [Int] e tÃnhamos um tipo inicial de Int. Int é um supertipo de List [Int].
A segunda restrição de dobra é que o valor inicial deve ser neutro, ou seja, não deve alterar o resultado. Por exemplo, o valor neutro para uma operação de adição seria 0, 1 para multiplicação, listas nulas, etc.
Esses são os fundamentos!