Field Notes

Scala/Play, Part 4: From Pattern Matching to map and getOrElse

Scala/Play, Part 4: From Pattern Matching to map and getOrElse

In the previous Scala/Play lesson, I used pattern matching to handle a policy lookup that might find nothing. That made both outcomes visible: a Some(policy) branch and a None branch. Now I want to express a smaller operation: take the policy number if a policy exists, and decide separately what to display if it does not.

This is where map and getOrElse became useful to me. I can derive both from the match expressions I already understand. The examples remain plain Scala 2.13.18 in our learning lab; Play 3 and Pekko are the direction of the course, not dependencies in this lesson.

Zakaria's navy-hoodie mascot at an alpine cable-car station, with a ticket in one gondola, an empty gondola, and a spare ticket in his hand
A present value and an absent value need different handling. The code below makes that decision explicit.

Start with the result type

Our hardcoded lookup returns Option[PolicySnapshot]. For POL-001, it produces Some(PolicySnapshot("POL-001", 600, 3)). An unknown number produces None. These are fictional data; there is no database involved.

To obtain the number while retaining possible absence, I need an Option[String]. Here is the implementation using familiar syntax:

def policyNumberWithMatch(
  result: Option[PolicySnapshot]
): Option[String] = {
  result match {
    case Some(policy) => Some(policy.number)
    case None => None
  }
}

The successful branch reads a String and wraps it in Some. The missing branch preserves None. Unlike our earlier description method, this method does not replace absence with a message.

Give map the transformation

That present-value transformation can be written with map:

def policyNumberWithMap(
  result: Option[PolicySnapshot]
): Option[String] = {
  result.map((policy: PolicySnapshot) => policy.number)
}

The new piece is a lambda: (policy: PolicySnapshot) => policy.number. It declares one input named policy, gives that input a type, and returns its number. The body produces a plain String. It does not construct an Option.

For a Java developer, its shape resembles (PolicySnapshot policy) -> policy.number(), assuming a Java type with that accessor. Scala uses => for the lambda arrow. In our earlier case Some(policy) => ..., the same token separated a pattern from its branch expression; the surrounding syntax tells us which role it has.

On Some(policy), map calls the function and wraps its result in Some. On None, it returns None without calling the function. That is the behavior documented by the Scala 2.13.18 Option API. The whole method still returns an optional value.

Choose a fallback at the display boundary

A screen label needs a String. For this example, I choose the text "No policy number" when the number is missing. I keep that decision separate from the lookup:

def policyNumberOrFallback(
  result: Option[PolicySnapshot]
): String = {
  val number: Option[String] = policyNumberWithMap(result)
  number.getOrElse("No policy number")
}

For Some("POL-001"), getOrElse returns "POL-001". For None, it returns the fallback. In this example both possibilities are Strings, so the result is a String. The original Option is unchanged.

The equivalent match makes the operation easy to check:

val display: String = number match {
  case Some(value) => value
  case None => "No policy number"
}
Two paths through map and getOrElse: a present policy becomes Some of its number then a String; None remains None through map then becomes the fallback String
map preserves the optional result. getOrElse supplies the final value for this display decision.

Read the chain one operation at a time

With the intermediate types understood, I can combine the operations:

result
  .map((policy: PolicySnapshot) => policy.number)
  .getOrElse("No policy number")

The stages are Option[PolicySnapshot], then Option[String], then String. Keeping that sequence visible helped me more than memorising a compact expression.

There is also a useful evaluation detail: the fallback expression is evaluated only for None. If I write number.getOrElse(buildFallback()), the method call happens only when the number is absent. If I calculate a fallback in a separate val beforehand, that calculation has already happened. I do not need the underlying parameter syntax yet to understand this observable behavior.

Run both outcomes

I added these methods to src/main/scala/learning/OptionLesson.scala. Its run method exercises both the known and unknown policy. From the lab directory, the explicit entry-point command is:

sbt "runMain learning.PremiumLesson"

This selects the lesson even when another practice object has its own main method. The verified output includes:

Match, found: Some(POL-001)
Map, found: Some(POL-001)
Match, missing: None
Map, missing: None
Found display: POL-001
Missing display: No policy number

The lesson edits were compiled and run locally; they have not yet been pushed to the public repository. I keep optional data while absence matters to the logic, then choose a fallback where that choice belongs. A display label is one such place. The next question is what happens when the transformation itself returns another Option.