Adam Warski shared code similar to this image during his video titled “Why Scala?”:
(To see his exact code, see that video.)
allStreets, flatMap, case statements, and function literals
His allStreets
function is interesting on several levels, so I am reproducing it here. In case what he’s doing is too difficult for new Scala developers, I’m also simplifying it, as shown here:
/**
* [1] When working with collections, `flatMap` is like `map` + `flatten`.
* @see https://alvinalexander.com/scala/collection-scala-flatmap-examples-map-flatten
* [2] A sequence of cases in curly braces can be used anywhere a function literal can be used.
* @see https://www.artima.com/pins1ed/case-classes-and-pattern-matching.html#15.7
*
* flatMap[B](f: A => IterableOnce[B]): List[B] // generic
* flatMap (f: Entity => IterableOnce[Address]): List[Address] // our case
*/
def allStreets(es: Set[Entity]): Set[String] =
val listAddys: Set[Address] = es.flatMap { // entity =>
case Person(fName, lName, listAddys) => listAddys.toSet
case Company(cname, loc) => Set(loc)
}
val listStreets: Set[String] = listAddys.map(address => address.street)
listStreets
As shown, he uses flatMap
to extract the entities from the Set[Entity]
that’s passed into the function.
One thing to notice is that inside the curly braces he uses two case
statements. As the Artima note I added to the Scaladoc states, “a sequence of cases in curly braces can be used anywhere a function literal can be used.” So that code inside the curly braces is just a function (or anonymous function, also known as a function literal).
Both case
statements yield a Set[Address]
, so listAddys
has that type.
As I note in my Scala flatMap examples page, when working with collections like
List
andSet
, you can think offlatMap
as being named “mapFlat
,” by which I mean “map
followed byflatten
.”
Then his code uses map
to extract all the street
fields out of those addresses. In the end, the function yields all those street
fields a Set[String]
.
Note that he uses a Set
here instead of a List
. I assume that’s because he doesn’t want any duplicate streets, and a Set
can’t have duplicates, while a List
can.
More on using 'case' statements as a function literal
Note that those case
statements inside the curly braces could have been written as a match
expression (match
/case
), like this:
def allStreets(es: Set[Entity]): Set[String] =
val listAddys: Set[Address] = es.flatMap { entity => entity match
case Person(fName, lName, listAddys) => listAddys.toSet
case Company(cname, loc) => Set(loc)
}
val listStreets: Set[String] = listAddys.map(address => address.street)
listStreets
I don’t know the history of why case
statements are allowed, as in the first example, but if using a match
expression like this is easier to read, feel free to use it instead.
A complete source code example
If you want a complete Scala source code example to play with, here you go:
case class Address(street: String, houseNumber: String, flatNumber: Option[Int])
sealed trait Entity
case class Person(firstName: String, lastName: String, addresses: List[Address]) extends Entity
case class Company(name: String, hq: Address) extends Entity
/**
* [1] When working with collections, `flatMap` is like `map` + `flatten`.
* @see https://alvinalexander.com/scala/collection-scala-flatmap-examples-map-flatten
* [2] A sequence of cases in curly braces can be used anywhere a function literal can be used.
* @see https://www.artima.com/pins1ed/case-classes-and-pattern-matching.html#15.7
*
* flatMap[B](f: A => IterableOnce[B]): List[B] // generic
* flatMap (f: Entity => IterableOnce[Address]): List[Address] // our case
*/
def allStreets(es: Set[Entity]): Set[String] =
val listAddys: Set[Address] = es.flatMap { // entity =>
case Person(fName, lName, listAddys) => listAddys.toSet
case Company(cname, loc) => Set(loc)
}
val listStreets: Set[String] = listAddys.map(address => address.street)
listStreets
val aa = Person(
"Alvin",
"Alexander",
List(
Address(
"200 I Street",
"Unit 1",
Option.empty[Int])
)
)
val vp = Company(
"Valley Programming",
Address(
"123 Main Street",
"Unit A",
Option.empty[Int])
)
@main def AdamWarski =
println( allStreets(Set(aa, vp)) ) // OUTPUT: Set("200 I Street", "123 Main Street")
// another flatMap example
import scala.util.control.Exception.allCatch
def makeInt(s: String): Option[Int] = allCatch.opt(s.toInt)
val strings = List("1", "hi", "3")
val ints: List[Int] = strings.flatMap(s => makeInt(s))
println(ints) // OUTPUT: List(1, 3)
Note that I include a second, simpler example at the end of that @main
method. If flatMap
seems a little difficult to grok, I hope they will both be helpful.
More details on flatMap and using case statements as function literals
Here are a few links with more details on these topics:
- My collection of Scala flatMap examples
- Erik Bruchez’s post, Partial functions without a PhD
- The Artima link