13

Basically I'm looking for the most scala-like way to do the following:

def sum(value1: Option[Int], value2: Option[Int]): Option[Int] = 
  if(value1.isDefined && value2.isDefined) Some(value1.get + value2.get)
  else if(value1.isDefined && value2.isEmpty) value1
  else if(value1.isEmpty && value2.isDefined) value2
  else None

This gives correct output:

sum(Some(5), Some(3))  // result = Some(8)
sum(Some(5), None)     // result = Some(5)
sum(None, Some(3))     // result = Some(3)
sum(None, None)        // result = None

Yet to sum more than two options I'd have to use way too many ifs or use some sort of loop.

EDIT-1:

While writing the question I came up with sort of an answer:

def sum2(value1: Option[Int], value2: Option[Int]): Option[Int] = 
  value1.toList ::: value2.toList reduceLeftOption { _ + _ }

This one looks very idiomatic to my inexperienced eye. This would even work with more than two values. Yet is possible to do the same without converting to lists?

EDIT-2:

I ended up with this solution (thanks to ziggystar):

def sum(values: Option[Int]*): Option[Int] = 
  values.flatten reduceLeftOption { _ + _ }

EDIT-3:

Another alternative thanks to Landei:

def sum(values: Option[Int]*): Option[Int] = 
  values collect { case Some(n) => n } reduceLeftOption { _ + _ }
giampaolo
  • 6,906
  • 5
  • 45
  • 73
Vilius Normantas
  • 3,708
  • 6
  • 25
  • 38

5 Answers5

10

How about:

scala> def sum(values: Option[Int]*): Option[Int] = values.flatten match {
     | case Nil => None                                                   
     | case l => Some(l.sum)                                              
     | }
sum: (values: Option[Int]*)Option[Int]

scala> sum(Some(1), None)
res0: Option[Int] = Some(1)

scala> sum(Some(1), Some(4))
res1: Option[Int] = Some(5)

scala> sum(Some(1), Some(4), Some(-5))
res3: Option[Int] = Some(0)

scala> sum(None, None)                
res4: Option[Int] = None

Edit

Maybe it would be sane to return 0 if all arguments were None. In that case the function would reduce to values.flatten.sum.

ziggystar
  • 28,410
  • 9
  • 72
  • 124
7
scala> def sum(a: Option[Int], b: Option[Int]) = (a,b) match {
     |   case (Some(x), Some(y)) => Some(x + y)
     |   case (Some(x), None) => Some(x)
     |   case (None, Some(y)) => Some(y)
     |   case _ => None
     | }
sum: (a: Option[Int],b: Option[Int])Option[Int]

scala> sum(Some(5), Some(3))
res0: Option[Int] = Some(8)

scala> sum(Some(5), None)
res1: Option[Int] = Some(5)

scala> sum(None, Some(3))
res2: Option[Int] = Some(3)

scala> sum(None, None)
res3: Option[Int] = None
michael.kebe
  • 10,916
  • 3
  • 44
  • 62
  • Thanks. It is more idiomatic in a sense that is uses `match` instead of `if`. Otherwise it's the same code as my first example, with the same limitations - you'd have to add much more `case`s to sum more than 2 values. – Vilius Normantas Apr 29 '11 at 14:57
5

You can make it very concise using the fact that there is an Semigroup instance for Option that does exactly what you want. You can use scalaz or cats. Here is an example using cats:

import cats.std.option._
import cats.syntax.semigroup._
import cats.std.int._

Option(1) |+| Option(2) // Some(3)
Option(1) |+| None      // Some(1)
None      |+| Option(2) // Some(2)

So your sum becomes:

def sum(v1: Option[Int], v2: Option[Int]): Option[Int] = v1 |+| v2
Peter Neyens
  • 9,770
  • 27
  • 33
Markus1189
  • 2,829
  • 1
  • 23
  • 32
5

Another solution is:

def sum(values: Option[Int]*): Int = values.collect{case Some(n) => n}.sum

While in the current case flatten is clearly more convenient, the collect version is more flexible, as it allows to perform mappings and to have additional filter conditions or complex patterns. E.g. imagine you want to have the sum of the squares of all even numbers in values:

values.collect{case Some(n) if n mod 2 == 0 => n*n}.sum
Landei
  • 54,104
  • 13
  • 100
  • 195
  • The compiler complains about returned type: found `Int`, required `Option[Int]`. I see your point about `collect` method instead of `flatten`, but I still have to do `reduceLeftOption` to get the desired result. – Vilius Normantas Apr 30 '11 at 05:50
4

Reduced solution of michael.kebe with a little look to some basic mathematical rules:

def sum(a: Option[Int], b: Option[Int]) = (a,b) match {
  case (None,None) => None
  case _ => Some(a.getOrElse(0)+b.getOrElse(0))
}

scala> sum(Some(5), Some(3))  // result = Some(8)
res6: Option[Int] = Some(8)

scala> sum(Some(5), None)     // result = Some(5)
res7: Option[Int] = Some(5)

scala> sum(None, Some(3))     // result = Some(3)
res8: Option[Int] = Some(3)

scala> sum(None, None)        // result = None
res9: Option[Int] = None
Antonin Brettsnajdr
  • 4,073
  • 2
  • 20
  • 14