Vasiliy Galkin
Vasiliy Galkin

Reputation: 2036

Can I return Map collection in Scala using for-yield syntax?

I'm fairly new to Scala, so hopefully you tolerate this question in the case you find it noobish :)

I wrote a function that returns a Seq of elements using yield syntax:

def calculateSomeMetrics(names: Seq[String]): Seq[Long] = {
  for (name <- names) yield {
    // some auxiliary actions
    val metrics = somehowCalculateMetrics()
    metrics
  }
}

Now I need to modify it to return a Map to preserve the original names against each of the calculated values:

def calculateSomeMetrics(names: Seq[String]): Map[String, Long] = { ... }

I've attempted to use the same yield-syntax but to yield a tuple instead of a single element:

def calculateSomeMetrics(names: Seq[String]): Map[String, Long] = {
  for (name <- names) yield {
    // Everything is the same as before
    (name, metrics)
  }
}

However, the compiler interprets it Seq[(String, Long)], as per the compiler error message

type mismatch;
  found   : Seq[(String, Long)]
  required: Map[String, Long]

So I'm wondering, what is the "canonical Scala way" to implement such a thing?

Upvotes: 2

Views: 1491

Answers (3)

Vasiliy Galkin
Vasiliy Galkin

Reputation: 2036

Several links here that either other people pointed me at or I managed to find out later on, just assembling them in a single answer for my future reference.

Upvotes: 1

G&#225;bor Bakos
G&#225;bor Bakos

Reputation: 9100

The efficient way of creating different collection types is using scala.collection.breakOut. It works with Maps and for comprehensions too:

import scala.collection.breakOut

val x: Map[String, Int] = (for (i <- 1 to 10) yield i.toString -> i)(breakOut)

x: Map[String,Int] = Map(8 -> 8, 4 -> 4, 9 -> 9, 5 -> 5, 10 -> 10, 6 -> 6, 1 -> 1, 2 -> 2, 7 -> 7, 3 -> 3)

In your case it should work too:

import scala.collection.breakOut

def calculateSomeMetrics(names: Seq[String]): Map[String, Long] = {
  (for (name <- names) yield {
    // Everything is the same as before
    (name, metrics)
  })(breakOut)
}

Comparison with toMap solutions: before toMap creates an intermediate Seq of Tuple2s (which incidentally might be a Map too in certain cases) and from that it creates the Map, while breakOut omits this intermediate Seq creation and creates the Map directly instead of the intermediate Seq.

Usually this is not a huge difference in memory or CPU usage (+ GC pressure), but sometimes these things matter.

Upvotes: 8

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149538

Either:

def calculateSomeMetrics(names: Seq[String]): Map[String, Long] = {
  (for (name <- names) yield {
    // Everything is the same as before
    (name, metrics)
  }).toMap
}

Or:

names.map { name =>
  // doStuff
  (name, metrics)
}.toMap

Upvotes: 6

Related Questions