Mike Slinn

Combinators

— Draft —

Published 2013-07-26. Last modified 2019-07-12.
Time to read: 9 minutes.

This lecture discusses combinators, which define the data flow of a functional program, and monads, which are a central concept of functional programming. The Idioms and Writing for Clarity sections of this lecture address writing style.

The sample code for this lecture can be found in courseNotes/src/main/scala/Combinators.scala.

A combinator transforms input data into output data. Chaining combinators together defines the data flow of a functional program. The data pipeline computes results in a predictable manner because combinators perform transformations that are idempotent, which is a synonym for referentially transparent. In order to be idempotent, combinator implementations must not reference external state. Another way of saying this is that combinators must not have any bound variables.

Combinators have no bound variables,
so they are referentially transparent

Violating Referential Transparency

Here is an example program that shows how to violate this principle; this happens more often than you might think. This example uses Akka to periodically run a task that modifies some state which is accessed from a method in another object. We will explore Akka later in this course. For now, we’ll just treat the scheduling code as a black box that somehow modifies externallyBoundVar every 50 milliseconds.

Scala code
object BoundAndGagged extends App {
  case class Blarg(i: Int, s: String)

  object OtherContext {
    var externallyBoundVar = Blarg(0, "")
  }

  import OtherContext.externallyBoundVar

  case class BadContainer(blarg: Blarg) {
    /** This function is not a combinator because it accesses boundVar, which is external state.
BAD! */
    def unpredictable(f: Blarg => Blarg): Blarg = f(blarg.copy(i=blarg.i + externallyBoundVar.i))
  }

  // start of black box
  import akka.actor.ActorSystem
  import concurrent.duration._
  import concurrent.ExecutionContext.Implicits.global

  val system = ActorSystem()
  // Continuously modify externallyBoundVar on another thread
  system.scheduler.schedule(0 milliseconds, 50 milliseconds) {
    externallyBoundVar =  if (System.currentTimeMillis % 2 == 0) externallyBoundVar else externallyBoundVar.copy(i=externallyBoundVar.i + 1)
  }
  // end of black box

  val blarg = Blarg(1, "hello")
  do {
    val badContainer = BadContainer(blarg).unpredictable { _.copy(i=blarg.i*2) }
    println(s"badContainer returned ${badContainer.i}")
    if (badContainer.i>=3) {
      system.terminate()
      sys.exit(0)
    }
  } while (true)
}

The problem with this code is that BadContainer.unpredictable’s output is not merely a function of the BadContainer constructor parameters and the function passed into unpredictableexernallyBoundVar also affects the output of unpredictable. This means that the declared inputs to BadContainer and unpredictable don’t correspond to well-defined output.

In other words, each time you run unpredictable for a given set of inputs you have no idea what the returned value might be. For this reason, unpredictable is not a combinator.

You can run this program as follows:

Shell
$ sbt "runMain BoundAndGagged"
badContainer returned 3 

Monads

Monads are a mathematical concept. From a practical point of view, Scala monads are just container classes that implement three standard combinators: map, flatMap and filter. Examples of monads are Option, and each of the collection classes such as Set, List, Vector and Map, as well as Future, which we will learn about in a series of lectures beginning with MultiThreading. Monads have predictable behavior because they implement the three required combinators, plus a few others that are expected by convention: collect, flatten and foreach.

Many other combinators have come to known by convention for monads. For example, Traversable, which as you know is inherited by all Scala collections, defines dozens of combinators, including exists, fold, groupBy, head and partition.

Transforming Data with Combinators

The map and flatMap combinators both transform data stored in monads, and they share two useful properties.

  1. The result of invoking either of these combinators from a monad results in a new instance of the same type of monad.
  2. The type of data contained by the resulting monad can be different than the type of data contained in the original monad.

This cartoon has been floating around the interwebs since at least 2015, but I cannot determine the author:

map

Map is used to create a monad that may have a different type or value of contents from the original monad.

For example, let’s transform a collection of Int into a new collection of Int, but transform the contents so they are divided by 2. We’ll use a Vector to hold the collection of Int.

Scala REPL
scala> val vector = Vector(0, 1, 2, 3)
vector: scala.collection.immutable.Vector[Int] = Vector(0, 1, 2, 3)
scala>
vector.map( _ / 2 ) res0: scala.collection.immutable.Vector[Int] = Vector(0, 0, 1, 1)

Notice that the result is a Vector, which has the same type as the input, vector. The type of the data contained within the input Vector (Int) was the same for the output Vector, but this will not always be the case. For example, we can convert each Int in vector into a String by using map like this.

Scala REPL
scala> vector.map { i => "abcdefg".substring(0, 1 + i) }
res1: scala.collection.immutable.Vector[String] = Vector(a, ab, abc, abcd) 
If a lambda function or a block of code contains more than one statement,
curly braces must be used

Notice that I used { curly braces } around the lambda function passed to map. I could have used ( parentheses ), however I used curly braces because the lambda function contained parentheses and I felt that using curly braces delimited the lambda block better, and thereby made the code easier to read.

Identity Transform (Supplemental)

The identity transform returns a monad with the same data as the original monad.

Scala REPL
scala> vector.map(identity)
res2: scala.collection.immutable.Vector[Int] = Vector(0, 1, 2, 3) 

I have only needed identity a few times. None of the examples were simple or straightforward. If I find a readily digestible example I will of course show it. Until then, this is reserved for cocktail hour at a Scala conference to show mastery of Scala trivia.

Here is a useless example.

Scala REPL
scala> List(List(1), List(2), List(3)).flatten
res3: List[Int] = List(1, 2, 3) 

... is the same as:

Scala REPL
scala> List(List(1), List(2), List(3)).flatMap(identity)
res4: List[Int] = List(1, 2, 3) 

flatten and flatMap are both discussed later in this lecture.

Option and the fold Method

Option.map(someOperation).getOrElse(defaultValue) is a very common idiom. For Options, since an Option is a monad that contains a single item, the fold combinator can be used instead. For Options, you can think of fold as being equivalent to.

Scala code
def fold(defaultValue)(someOperation)

For example, given the following.

Scala code
Some(42).map(_.toString).getOrElse("help!")

We can rewrite it using fold this way.

Scala code
Some(42).fold("help!")(_.toString)

flatten

flatten removes the wrappings around a sequence of wrapped items and returns an new sequence with just the unwrapped values. As an example, lets flatten a List of Lists. Remember that Nil is a synonym for the empty list, which could also be written as List().

Scala REPL
scala> List(List(1), Nil, List(2,3)).flatten
res5: List[Int] = List(1, 2, 3) 

Another example: given a sequence of Option, flatten will just return the elements with values wrapped in instances of Some; flatten ignores sequence elements which are Nil, None or null.

Scala REPL
scala> val vector2 = Vector(Some(1), None, Some(3), Some(4))
vector2: scala.collection.immutable.Vector[Option[Int]] = Vector(Some(1), None, Some(3), Some(4)) 

Invoking flatten will unwrap the Some instances, discard the None references and leave us with a Vector[Int].

Scala REPL
scala> vector2.flatten
res6: Vector[Int] = Vector(1, 3, 4) 

flatMap

flatMap also flattens a sequence, but in addition flatMap accepts a function value which is applied to each sequence item, thereby transforming the sequence while flattening it.

In the following code example, the wrapped items with values (Some(1), Some(3) and Some(4)), are unwrapped and passed to the lambda function. This means that the None items are not passed to the flatMap’s lambda.

Scala REPL
scala> vector2.flatMap { _.map(_*2) }
res7: scala.collection.immutable.Vector[Int] = Vector(2, 6, 8) 

We can write this same code using a for-comprehenshion. The compiler desugars (removes the syntactic sugar) of the for-comprehension and generates code similar to the flatMap / map code above.

Scala REPL
scala> for { maybeItem <- vector2; item <- maybeItem } yield item * 2
res8: scala.collection.immutable.Vector[Int] = Vector(2, 6, 8) 

Compare the result of flatMap / map with that of map / map.

Scala REPL
scala> vector2.map { _.map(_*2) }
res9: scala.collection.immutable.Vector[Option[Int]] = Vector(Some(2), None, Some(6), Some(8)) 

Supporting Combinators

The filter in the next statement creates a new collection of the same type that only contains elements which pass the predicate; only even Ints are included in the resulting collection.

Scala REPL
scala> vector.filter( _%2==0 )
res9: Vector[Int] = Vector(0, 2) 

filterNot includes the elements that fail the predicate test.

Scala REPL
scala> vector.filterNot( _%2==0 )
res10: Vector[Int] = Vector(1, 3) 

If you wanted both lists to be generated at once, that is, a Vector of all items that pass the predicate and another Vector of all items that fail the predicate, you can use the partition combinator.

Scala REPL
scala> vector.partition( _%2==0 )
res11: (Vector[Int], Vector[Int]) = (Vector(0, 2),Vector(1, 3)) 

The above returned two lists, as you can see. These lists can be captured with one assignment, like this.

Scala REPL
scala> val (pass, fail) = vector.partition( _%2==0 )
pass: Vector[Int] = Vector(0, 2)
fail: Vector[Int] = Vector(1, 3)
scala>
pass res12: Vector[Int] = Vector(2)
scala>
fail res13: Vector[Int] = Vector(1, 3)

If you are concerned that readers of your code might be unclear as to the type of the assigned variables, you can explicitly declare their types.

Scala REPL
scala> val (pass: Vector[Int], fail: Vector[Int]) = vector.partition( _%2==0 )
pass: Vector[Int] = Vector(0, 2)
fail: Vector[Int] = Vector(1, 3) 

You can prune out duplicate items from any collection that mixes in Seq such as Vector and List by using the distinct method.

Scala REPL
scala> Vector(1, 2, 2, 3).distinct
res14: Vector[Int] = Vector(1, 2, 3) 

exists, find and forall are similar. exists returns true if a predicate is true for at least one element in a collection. find returns an Option containing the first element found in a collection that fulfills a predicate. forall only returns true if a predicate is true for all members of a vector.

Scala REPL
scala> vector.exists(_%2==0)
res15: Boolean = true
scala>
vector.find(_%2==0) res16: Option[Int] = Some(0)
scala>
vector.forall(_%2==0) res17: Boolean = false

Maps and groupBy

The map method is distinct from the mutable.Map and immutable.Map types. A Map is a collection of name/value pairs, often provided to the constructor or map builder as Tuple2 instances. The map combinator method accepts an operation that will be applied to each element of a collection. Yes, you can write code like this, but you should not.

Scala REPL
scala> val map = Map(1 -> "a", 2 -> "b", 3 -> "c", 4 -> "d")
map: scala.collection.immutable.Map[Int,String] = Map(1 -> a, 2 -> b, 3 -> c, 4 -> d)
scala>
map.map { nv => (nv._1 * 3, nv._2 * 3) } res18: scala.collection.immutable.Map[Int,String] = Map(3 -> aaa, 6 -> bbb, 9 -> ccc, 12 -> ddd)

However, you would be more popular with the programmers who come after you if you renamed the map variable to something more descriptive. For example, depending on what the variable is used for, it might be called initialValues.

Scala REPL
scala> val initialValues = Map(1 -> "a", 2 -> "b", 3 -> "c", 4 -> "d")
map: scala.collection.immutable.Map[Int,String] = Map(1 -> a, 2 -> b, 3 -> c, 4 -> d)
scala>
initialValues.map { nv => (nv._1 * 3, nv._2 * 3) } res18: scala.collection.immutable.Map[Int,String] = Map(3 -> aaa, 6 -> bbb, 9 -> ccc, 12 -> ddd)

You can use filter to create a copy of a Map with certain tuples filtered out. The following two syntaxes are equivalent – they both return a new Map containing only even keys.

Scala REPL
scala> initialValues.filter( x => x._1 % 2 == 0 )
res19: scala.collection.immutable.Map[Int,String] = Map(2 -> b, 4 -> d)
scala>
initialValues.filter( _._1 % 2 == 0 ) res20: scala.collection.immutable.Map[Int,String] = Map(2 -> b, 4 -> d)

groupBy is a powerful Map method. Here we create a Map of Maps: one Map that contains only even keys, and another Map containing only odd keys. The predicate passed into groupBy determines the destination Map that each name/value tuple in the original Map is assigned to.

Scala REPL
scala> val group = initialValues.groupBy(_._1%2==0)
group: scala.collection.immutable.Map[Boolean,scala.collection.immutable.Map[Int,String]] = Map(false -> Map(1 -> eh, 3 -> sea),
true -> Map(2 -> bee, 4 -> d)) 

We can retrieve the submaps resulting from the groupBy combinator easily.

Scala REPL
scala> group(true)
res21: scala.collection.immutable.Map[Int,String] = Map(2 -> b, 4 -> d)
scala>
group(false) res22: scala.collection.immutable.Map[Int,String] = Map(1 -> eh, 3 -> sea)

Here is a better way of getting the tuples with even and odd keys from a Map.

Scala REPL
scala> val (even, odd) = iscala> nitialValues.partition(_._1%2==0)
even: scala.collection.immutable.Map[Int,String] = Map(2 -> bee, 4 -> d)
odd: scala.collection.immutable.Map[Int,String] = Map(1 -> eh, 3 -> sea) 

Idioms

General Collection Idioms

There are many ways to express yourself in Scala, and some are less obvious than others. Scala’s philosophy is to be reasonably brief, for some definition of ’reasonably’. You should learn the following equivalent expressions, and favor the shorter expression when writing code. The following will work on any Scala collection that inherits SeqLike, including Option, List and Map.

Given the following (the double Function is written using the placeholder syntax shorthand introduced in the Functions are First Class lecture of the Introduction to Scala course – see the Lambda Review & Drill lecture if you need practice).

Scala REPL
scala> val collection = List(1, 2, 3)
collection: List[Int] = List(1, 2, 3)
scala>
val double = (_:Int) * 2 double: Int => Int = <function1>

To remind you of how the double Function works.

Scala REPL
scala> double(21)
res23: Int = 42 

Then the following holds. Prefer the right-most of each pair of equivalent expressions (the underscores are examples of the higher-order shorthand syntax introduced in the Higher-Order Functions lecture of this course).

count

Awkward Expression Preferred Equivalent Expression
collection.filter(_ > 0).size collection.count(_ > 0)

Count is good for discovering how many items in a collection satisfy a predicate. In this case, the predicate is the lambda _ > 0. The awkward expression actually defines how count works: filter out all the items in the collection that do not satisfy the predicate, and obtain the size of the collection of the remaining items which do satisfy the predicate.

Scala REPL
scala> collection.filter(_ > 0).size
res0: Int = 3
scala>
collection.count(_ > 0) res1: Int = 3

nonEmpty

Awkward Expression Preferred Equivalent Expression
!collection.isEmpty collection.nonEmpty

While isEmpty returns true if the collection has no items in it, you don’t want to write a double negative in order to test if the collection has any items. The nonEmpty property returns true if the collection has at least one item in it.

Scala REPL
scala> collection.isEmpty
res14: Boolean = false
scala>
!collection.isEmpty res15: Boolean = true
scala>
collection.nonEmpty res16: Boolean = true

exists

Awkward Expression Preferred Equivalent Expression
collection.filter(_ >= 2).length >= 1 collection.exists(_ >= 2)
collection.find(double(_) == 2).isDefined collection.exists(double(_) == 2)

If you want to discover if a collection contains at least one item whose value satisfies a predicate, use exists instead of testing the result of filter or find.

Scala REPL
scala> collection.filter(_ >= 2).length >= 1
res4: Boolean = true
scala>
collection.exists(_ == 2) res3: Boolean = true
scala>
collection.find(double(_) == 2).isDefined res17: Boolean = true
scala>
collection.exists(double(_) == 2) res18: Boolean = true

contains

Awkward Expression Preferred Equivalent Expression
collection.find(_ == 2).isDefined collection.contains(2)
collection.exists(_ == 2) collection.contains(2)

To discover if a collection contains a value, use contains instead of testing the result of find or calling exists and comparing to the desired value.

Scala REPL
scala> collection.find(_ == 2).isDefined
res19: Boolean = true
scala>
collection.contains(2) res20: Boolean = true
scala>
collection.exists(_ == 2) res21: Boolean = true

sum

Awkward Expression Preferred Equivalent Expression
collection.fold(0)(_ + _) collection.sum
collection.foldLeft(0)(_ + _) collection.sum

To add up all the values of a collection, use sum instead of calling fold or foldLeft.

Scala REPL
scala> collection.fold(0)(_ + _)
res22: Int = 6
scala>
collection.sum res23: Int = 6
scala>
collection.foldLeft(0)(_ + _) res24: Int = 6

max and min

Awkward Expression Preferred Equivalent Expression
collection.reduce(_ min _) collection.min
collection.reduce((x, y) => math.min(x, y)) collection.min
collection.reduceLeft(_ min _) collection.min

Call min or max to find the minimum or maximum value of a collection, instead of calling reduce or reduceLeft and passing in a lambda that invokes min or max.

Scala REPL
scala> collection.reduce(_ min _)
res25: Int = 1
scala>
collection.min res26: Int = 1
scala>
collection.reduce((x, y) => math.min(x, y)) res27: Int = 1
scala>
collection.reduceLeft(_ min _) res28: Int = 1
scala>
collection.max res29: Int = 3

Idioms Specific to Option

Given:

Scala REPL
scala> val maybeHead = collection.headOption
maybeHead: Option[Int] = Some(1) 

... then the following holds. Again, prefer the right-most expression.

Awkward Expression Preferred Equivalent Expression
maybeHead.map(_ + 1).getOrElse(0) maybeHead.fold(0)(_ + 1)

Idioms Specific to Map and subclasses of MapOps

Given:

Scala REPL
scala> val aMap = Map(’a’ -> 2)
aMap: scala.collection.immutable.Map[Char,Int] = Map(a -> 2) 

... then the following holds. Again, prefer the right-most expression.

Awkward Expression Preferred Equivalent Expression
aMap.get(’a’).getOrElse(0) aMap.getOrElse(’a’, 0)

Writing for Clarity

Functions vs. Methods

Higher order functions like map and flatMap accept lambda functions and regular named FunctionNs.

If you supply a method where a FunctionN is required, Scala automatically performs eta-expansion (also referred to as method lifting) and creates a FunctionN from the method. Scala also treats FunctionNs as if they were methods.

Anywhere you can write a method reference you can use a FunctionN instead. Conversely, anywhere you can write a FunctionN reference you can write a method reference instead. If you are writing a class or a trait that defines a method using a def keyword, you could also define a FunctionN.

The FunctionN will close over any variables or other methods that it references, such as x in this example. We discussed closures in the Closures lecture of the Introduction to Scala course.

Scala code
object FuncMeth extends App {
  class Klass {
    val x = 3
    def method1(y: Int) = s"x=$x and y=$y from method 1"
    val function1 = (y:Int) => s"x=$x and y=$y from function 1"
  }

  val klass = new Klass()
  println(klass.method1(4))
  println(klass.function1(4))
}

You can run this by typing.

Shell
$ sbt "runMain FuncMeth"
x=3 and y=4 from method 1
x=3 and y=4 from function 1 
Any fool can write code that a computer can understand.
Good programmers write code that humans can understand.

The point of this section is: if you need some code which will mostly be used as a method, write it as a method. If that code is mostly going to be used as a FunctionN, write a FunctionN. The only benefit is so a reader can understand what your purpose is. Since method lifting is done at compile time, your program will not run faster or slower if you write a method or a FunctionN.

Use Intermediate Variables and Declare Their Types

Here is some production code from the Silhouette open source project. It is hard to understand because the code offers no clue regarding the types used in the code. Instead, chains of combinators are written, one after the other. If one of the monads short-circuits there is no way to discover which monad terminated the flow, or why it short-circuited. This makes debugging difficult. This code is expensive to maintain as written.

This code is also hard to follow because types are declared as pure traits instead of concrete types, and the traits are bound to concrete types at runtime by Google Guice using dependency injection. There is no way to understand what this code does without spending considerable time analyzing it with an IDE.

This code uses the Future monad. We will discuss Futures over the course of several lectures later in this course, starting with MultiThreading. Futures are like any other monad, and the lesson taught here applies equally well to collection monads and any other type of monad.

def authenticate(loginInfo: LoginInfo, password: String): Future[State] = {
  authInfoRepository.find[PasswordInfo](loginInfo).flatMap {
    case Some(passwordInfo) => passwordHasherRegistry.find(passwordInfo) match {
      case Some(hasher) if hasher.matches(passwordInfo, password) =>
        if (passwordHasherRegistry.isDeprecated(hasher) || hasher.isDeprecated(passwordInfo).contains(true)) {
          authInfoRepository.update(loginInfo, passwordHasherRegistry.current.hash(password)).map { _ =>
            Authenticated
          }
        } else {
          Future.successful(Authenticated)
        }
      case Some(hasher) => Future.successful(InvalidPassword(PasswordDoesNotMatch.format(id)))
      case None => Future.successful(UnsupportedHasher(HasherIsNotRegistered.format(
        id, passwordInfo.hasher, passwordHasherRegistry.all.map(_.id).mkString(", ")
      )))
    }
    case None => Future.successful(NotFound(PasswordInfoNotFound.format(id, loginInfo)))
  }
}

Here is the same code, rewritten to show intermediate variables and types. This makes the code more understandable, debuggable and therefore maintainable. This version does not run noticeably slower. I also added some conditional log output, which also does not noticeably impact runtime performance.

def authenticate(loginInfo: LoginInfo, password: String): Future[State] = {
  val maybeAuthInfoRepo: Future[Option[PasswordInfo]] = authInfoRepository.find[PasswordInfo](loginInfo)
  maybeAuthInfoRepo.flatMap {
    case Some(passwordInfo) =>
      val maybeHasher: Option[PasswordHasher] = passwordHasherRegistry.find(passwordInfo)
      maybeHasher match {
        case Some(hasher) if hasher.matches(passwordInfo, password) =>
          if (passwordHasherRegistry.isDeprecated(hasher) || hasher.isDeprecated(passwordInfo).contains(true)) {
            val hashedPwd: PasswordInfo = passwordHasherRegistry.current.hash(password)
            val updateResult: Future[PasswordInfo] = authInfoRepository.update(loginInfo, hashedPwd)
            updateResult.map { _ =>
              logger.debug("Authenticated with deprecated password hasher!")
              Authenticated
            }
          } else {
            logger.debug("Authenticated with current password hasher!")
            Future.successful(Authenticated)
          }
        case Some(hasher) => Future.successful(InvalidPassword(PasswordDoesNotMatch.format(id)))
        case None => Future.successful(UnsupportedHasher(HasherIsNotRegistered.format(
          id, passwordInfo.hasher, passwordHasherRegistry.all.map(_.id).mkString(", ")
        )))
      }
    case None => Future.successful(NotFound(PasswordInfoNotFound.format(id, loginInfo)))
  }
}
Always code as if the person who ends up maintaining your code is a violent psychopath who knows where you live.

Exercise - HTTP URL-Form Encoded Parameters

This is typical problem when working with form parameters in the controller of a Play Framework application. The HTTP URL-Form encoded response body has type Map[String, List[String]], which means that each String parameter name can have a list of String values. The Map has no default value. The challenge is to write a method that returns Boolean value according to whether the map contains the key "active". If the key is present, and has the value List("true"), return true. Return false if the key is not present in the map, or if the value is List("false").

Here is some test data (emptyMap, trueMap and falseMap).

Scala REPL
scala> val emptyMap = Map.empty[String, List[String]]
emptyMap: scala.collection.immutable.Map[String,List[String]] = Map()
scala>
val trueMap = Map("active" -> List("true")) trueMap: scala.collection.immutable.Map[String,List[String]] = Map(active -> List(true))
scala>
val falseMap = Map("active" -> List("false")) falseMap: scala.collection.immutable.Map[String,List[String]] = Map(active -> List(false))

Solution

Here is a clumsy solution:

Scala REPL
scala> def clumsy(body: Map[String, List[String]]): Boolean =
body.get("active")
  .getOrElse(Nil)
    .map(_.toBoolean)
    .headOption
    .getOrElse(false)
clumsy: (body: Map[String,List[String]])Boolean 

Let’s try it out.

Scala REPL
scala> clumsy(emptyMap)
res0: Boolean = false
scala>
clumsy(trueMap) res1: Boolean = true
scala>
clumsy(falseMap) res2: Boolean = false

Here is how I arrived at this:

Scala REPL
scala> emptyMap.get("active")
res3: Option[List[String]] = None
scala>
falseMap.get("active") res4: Option[List[String]] = Some(List(false))
scala>
trueMap.get("active") res5: Option[List[String]] = Some(List(true))
scala>
emptyMap.get("active").getOrElse(Nil) res6: List[String] = List()
scala>
emptyMap.get("active").getOrElse(Nil).map(_.toBoolean) res7: List[Boolean] = List()
scala>
emptyMap.get("active").getOrElse(Nil).map(_.toBoolean).headOption res8: Option[Boolean] = None
scala>
emptyMap.get("active").getOrElse(Nil).map(_.toBoolean).headOption.getOrElse(false) res9: Boolean = false

Here is a better solution.

Scala REPL
scala> def better(body: Map[String, List[String]]): Boolean =
  body.getOrElse("active", List("false"))
    .head.toBoolean
better: (body: Map[String,List[String]])Boolean 

Let’s try it out:

Scala REPL
scala> better(emptyMap)
res10: Boolean = false
scala>
better(trueMap) res11: Boolean = true
scala>
better(falseMap) res12: Boolean = false

Here is how I arrived at this solution:

Scala REPL
scala> emptyMap.getOrElse("active", List("false"))
res13: List[String] = List(false)
scala>
trueMap.getOrElse("active", List("false")) res14: List[String] = List(true)
scala>
falseMap.getOrElse("active", List("false")) res15: List[String] = List(false)
scala>
emptyMap.getOrElse("active", List("false")).head res16: String = false
scala>
emptyMap.getOrElse("active", List("false")).head.toBoolean res17: Boolean = false

* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.