fracca
fracca

Reputation: 2437

Scala collections: Is there a safe map operation?

Let's say that I have a List (or the values in a Map), and i want to perform an operation on each item. But unfortunately, for whatever reason, this list of values can contain nulls.

scala> val players = List("Messi", null, "Xavi", "Iniesta", null)
players: List[java.lang.String] = List(Messi, null, Xavi, Iniesta, null)

In order to avoid blowing up with a NPE, i need to do the following:

scala> players.filterNot(_ == null ).map(_.toUpperCase)
res84: List[java.lang.String] = List(MESSI, XAVI, INIESTA)

Is there any better way of doing this?

Ideally something like:

players.safeMap(_.toUpperCase)

On the scala-language mailing list, Simon proposed this:

players.filter ( null !=).map(_.toUpperCase )

which is shorter version of my original take, and as short as you can get without a dedicated method.

Even better, Stefan and Kevin proposed the method withFilter which will return a lazy proxy, so both operations can be merged.

players.withFilter ( null !=).map(_.toUpperCase )

Upvotes: 2

Views: 2590

Answers (3)

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297155

I'd do this:

players flatMap Option map (_.toUpperCase)

But that's worse than collect. filter + map is always better done with collect.

Upvotes: 2

dhg
dhg

Reputation: 52681

You could convert to a list of Option[String]:

scala> val optionPlayers = players.map(Option(_))
optionPlayers: List[Option[java.lang.String]] = List(Some(Messi), None, Some(Xavi), Some(Iniesta), None)

Option is universally preferred to null and it gives you a lot of flexibility in how you can safely handle the data. Here's are thee easy ways to get the result you were looking for:

scala> optionPlayers.collect { case Some(s) => s.toUpperCase }
res0: List[java.lang.String] = List(MESSI, XAVI, INIESTA)

scala> optionPlayers.flatMap(_.map(_.toUpperCase))
res1: List[java.lang.String] = List(MESSI, XAVI, INIESTA)

scala> optionPlayers.flatten.map(_.toUpperCase)
res2: List[java.lang.String] = List(MESSI, XAVI, INIESTA)

You can find a lot more information about Option in other StackOverflow questions or by searching the web.

Or, you can always just define that safeMap method you wanted as an implicit on List:

implicit def enhanceList[T](list: List[T]) = new {
  def safeMap[R](f: T => R) = list.filterNot(_ == null).map(f)
}

so you can do:

scala> players.safeMap(_.toUpperCase)
res4: List[java.lang.String] = List(MESSI, XAVI, INIESTA)

Though if you define an implicit, you might want to use a CanBuildFrom style like the basic collections do to make it work on more than just List. You can find more information about that elsewhere.

Upvotes: 1

Julien Richard-Foy
Julien Richard-Foy

Reputation: 9663

If you can’t avoid nulls (e.g. if you get your list from Java code), another alternative is to use collect instead of map:

scala> players.collect { case player if player != null => player.toUpperCase }
res0: List[java.lang.String] = List(MESSI, XAVI, INIESTA)

Upvotes: 3

Related Questions