Sugar overload? + operator in for comprehension

I would like to know, what method or function really implements the + operator on the last line of the code snippet below.
Because, executed in the REPL, it allows me to add the values within two Options, when I cannot perform the same operation with the same Options, outside of a yield clause.

//1. Make a table mapping names to ages.
val db = Map("Brian" -> 27, "Frank" -> 22)

//2.1. get the entries as Option[Int], manually
val age1 = db.get("Brian")
val age2 = db.get("Frank")

//2.2. try to add them - the below code will fail because I am trying to add Options,
//     not the values inside them
//val ageSum = age1 + age2

//3.  However if I use a for comprehension, the + operator will "do the right thing"
//     and add the two Ints contained in the options
for
{
  a1 <- db.get("Brian")
  a2 <- db.get("Frank")
} yield (a1 + a2)

for desugars to foreach, flatMap, map, withFilter / filter depending on contents. Details are here: https://docs.scala-lang.org/tutorials/FAQ/yield.html

Also, if you use IntelliJ Scala plugin then you can use its automatic desugaring of for comprehensions. It’s not fully reliable, though (i.e. it doesn’t alway desugar in a way that Scala compiler does).

The + operator in the yields clause just adds two Ints.

for {
a1 <- db.get(“Brian”)
a2 <- db.get(“Frank”)
} yield (a1 + a2)

is, I think, sugar for

db.get(“Brian”).flatMap(a1 => db.get(“Frank”).map(a2 => a1 + a2))

So, a1 and a2 are parameters of functions that take Ints.

1 Like

@tarsa - in fact I am using Intellij, but its desugarer (?) puked on this snippet - just put brackets everywhere and broke compilation. But thanks for the tutorial link - I had searched this site before posting, but not found such a useful one.

@curoli - that is it. The flatMap (I think via an implicit conversion) turns the Option into an Iterable of its underlying value. Which can then work with the + operator. Thankyou.

In Scala 2.12.8 flatMap is defined directly on Option: https://github.com/scala/scala/blob/v2.12.8/src/library/scala/Option.scala#L187
In fact, that was even the case in Scala 2.7.6: https://github.com/scala/scala/blob/v2.7.6/src/library/scala/Option.scala#L76

Implicit conversion from Option to Iterable happens when you generate an Option inside flatMap over Iterable, e.g.

for {
  x <- List(1, 2, 3)
  y <- Some(x + 1)
} yield {
  x + y
}
// or equivalent
List(1, 2, 3).flatMap(x => Some(x + 1).map(y => x + y))

As an addendum I will note that @marracuene is stepping very close to both monoids and applicative functors here, which are abstractions provided by cats.

@ import cats.implicits._ 
import cats.implicits._

@ val (age1, age2) = (Option(42), Option(11)) 
age1: Option[Int] = Some(42)
age2: Option[Int] = Some(11)

@ age1 |+| age2 // because Option[Int] forms a monoid 
res4: Option[Int] = Some(53)

@ (age1, age2).mapN(_ + _) // because Option is an applicative functor 
res5: Option[Int] = Some(53)

So “lifting” an operation on Option so that it looks like an operation on the underlying type is definitely a thing, and it’s an interesting thing.