Expanding on Adam Warski’s flatMap example (Set, case statements, function literal)

Adam Warski shared code similar to this image during his video titled “Why Scala?”:

Adam Warski flatMap example and anonymous function with case statements

(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 and Set, you can think of flatMap as being named “mapFlat,” by which I mean “map followed by flatten.”

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: