Folding and reducing are two types of operations that are both very important with dealing with collections in Scala (and other functional languages). This topic covers the fold operations. The reduce topic was previously covered.
A simplistic explanation is they allow processing of the collections. I think about foldRight and foldLeft to be very similar to a visitor that visits each element and performs a calculation. It does not change the values of the collection instead it calculates a result from visiting each element. In Java you might see the following pattern:
- class Collection {
- pubic Result op( Result startingValue, Op operation){...}
- }
- interface Op{
- public Result op( Object nextCollectionElem, Result lastCalculatedResult)
- }
To a Java programmer this is pretty obvious how you use it. This is essentially the fold operation. The difference is we are using closures so there is almost no boilerplate and all collection/Iteratore/Iterable interfaces have the fold methods.
One last point before examples. For fold and reduce operations there are both foldLeft and foldRight and similarly reduceLeft and reduceRight. These indicate the order that a collection is processed. Note that for certain collections one direction is more performant that the other. Lists, for example, are better to process left to right because of the datastructure used.
Some tasks that are suited to fold are:
- Find the 5 smallest elements in a list
- Sum all the values of a Map together
- Sum the lengths of the Strings contained in the collection
There are a number of ways that fold can be written syntactically. The following examples are ways to sum together the integers of a Map. Note that /: is an alias for foldLeft and :/ is an alias for foldRight. Normally foldLeft and foldRight should be used unless the rest of the developers who will be reading the code are quite confortable reading Scala.
- scala> val m = Map(1 -> "one", 2 -> "two", 3 -> "three")
- m: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two, 3 -> three)
- scala> m.foldRight(0)((kv: (Int, String), a : Int) => a+kv._2.length)
- res1: Int = 11
- scala> (m :\ 0)((kv: (Int, String), a : Int) => a+kv._2.length)
- res5: Int = 11
- scala> m.foldRight(0){ case ((key, value), a) => a+value.length}
- res7: Int = 11
- scala> m.foldRight(0){ case (kv, a) => a+kv._2.length} }}
- res8: Int = 11
- scala> val m = Map("a" -> 1, "b" -> 2, "c" -> 3)
- m: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1, b -> 2, c -> 3)
- scala> m.foldLeft(0)( (accum, kv) => accum + kv._2 )
- res9: Int = 6
- scala> m.foldLeft(0){case (accum, (key, value)) => accum + value}
- res5: Int = 6
- scala> (0 /: m){case (accum, (key, value)) => accum + value}
- res4: Int = 6