Scala fold, foldLeft e foldRight

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).

  1. A função é então aplicada a seus dois argumentos, neste caso uma adição simples, e retorna o resultado.
  2. 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.
  3. 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.
  4. 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!