7

In scala, how do I define addition over two Option arguments? Just to be specific, let's say they're wrappers for Int types (I'm actually working with maps of doubles but this example is simpler).

I tried the following but it just gives me an error:

  def addOpt(a:Option[Int], b:Option[Int]) = {
    a match {
      case Some(x) => x.get
      case None => 0
    } + b match {
      case Some(y) => y.get
      case None => 0
    }
  }

Edited to add:

In my actual problem, I'm adding two maps which are standins for sparse vectors. So the None case returns Map[Int, Double] and the + is actually a ++ (with the tweak at stackoverflow.com/a/7080321/614684)

JasonMond
  • 1,440
  • 1
  • 16
  • 30
  • 2
    You don't extract the content of the option the proper way. When you do match case Some(x), x is the value inside the option(type Int) and you don't call get on that. case Some(x) => x. Anyay, if you want content or default, a.getOrElse(0) is more convenient. – Didier Dupont May 16 '12 at 14:07
  • @didierd THANK YOU! That was the answer I needed. Can you convert to an answer? I'll choose yours. – JasonMond May 16 '12 at 14:18
  • Obligatory reference: http://blog.tmorris.net/scalaoption-cheat-sheet/ – missingfaktor May 16 '12 at 16:30

5 Answers5

26

Monoids

You might find life becomes a lot easier when you realize that you can stand on the shoulders of giants and take advantage of common abstractions and the libraries built to use them. To this end, this question is basically about dealing with monoids (see related questions below for more about this) and the library in question is called scalaz.

Using scalaz FP, this is just:

def add(a: Option[Int], b: Option[Int]) = ~(a |+| b)

What is more this works on any monoid M:

def add[M: Monoid](a: Option[M], b: Option[M]) = ~(a |+| b)

Even more usefully, it works on any number of them placed inside a Foldable container:

def add[M: Monoid, F: Foldable](as: F[Option[M]]) = ~as.asMA.sum

Note that some rather useful monoids, aside from the obvious Int, String, Boolean are:

  1. Map[A, B: Monoid]
  2. A => (B: Monoid)
  3. Option[A: Monoid]

In fact, it's barely worth the bother of extracting your own method:

scala> some(some(some(1))) #:: some(some(some(2))) #:: Stream.empty
res0: scala.collection.immutable.Stream[Option[Option[Option[Int]]]] = Stream(Some(Some(Some(1))), ?)

scala> ~res0.asMA.sum
res1: Option[Option[Int]] = Some(Some(3))

Some related questions

Q. What is a monoid?

A monoid is a type M for which there exists an associative binary operation (M, M) => M and an identity I under this operation, such that mplus(m, I) == m == mplus(I, m) for all m of type M

Q. What is |+|?

This is just scalaz shorthand (or ASCII madness, ymmv) for the mplus binary operation

Q. What is ~?

It is a unary operator meaning "or identity" which is retrofitted (using scala's implicit conversions) by the scalaz library onto Option[M] if M is a monoid. Obviously a non-empty option returns its contents; an empty option is replaced by the monoid's identity.

Q. What is asMA.sum?

A Foldable is basically a datastructure which can be folded over (like foldLeft, for example). Recall that foldLeft takes a seed value and an operation to compose successive computations. In the case of summing a monoid, the seed value is the identity I and the operation is mplus. You can hence call asMA.sum on a Foldable[M : Monoid]. You might need to use asMA because of the name clash with the standard library's sum method.

Some References

  • Slides and Video of a talk I gave which gives practical examples of using monoids in the wild
Community
  • 1
  • 1
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
10
def addOpts(xs: Option[Int]*) = xs.flatten.sum

This will work for any number of inputs.

Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
  • 1
    FWIW, to make this work for `Map[Int, Double]`, it's simply `def addOpts(xs: Option[Map[Int, Double]]*) = xs.flatten.foldLeft(Map.empty[Int, Double]) { _ ++ _ }` – leedm777 May 16 '12 at 13:28
  • 1
    Indeed. Or the scalaz version which works with both: `def addOpts[T:Monoid](xs: Option[T]*) = xs.flatten.asMA.sum` – Luigi Plinge May 16 '12 at 14:28
5

If they both default to 0 you don't need pattern matching:

  def addOpt(a:Option[Int], b:Option[Int]) = {
    a.getOrElse(0) + b.getOrElse(0)
  }
Maurício Linhares
  • 39,901
  • 14
  • 121
  • 158
  • In my actual problem, they don't. I'm adding two maps which are standins for sparse vectors. – JasonMond May 16 '12 at 12:12
  • If you don't have a default for both, you can't use Option in this case, as one or both of them could be undefined. – Maurício Linhares May 16 '12 at 12:14
  • What if I make them default to Map[Int, Double] (which is what they actually are) and the `+` is actually a `++` (with the tweak at http://stackoverflow.com/a/7080321/614684). – JasonMond May 16 '12 at 12:38
4

(Repeating comment above in an answer as requested)

You don't extract the content of the option the proper way. When you match with case Some(x), x is the value inside the option(type Int) and you don't call get on that. Just do

case Some(x) => x 

Anyway, if you want content or default, a.getOrElse(0) is more convenient

Didier Dupont
  • 29,398
  • 7
  • 71
  • 90
0
def addOpt(ao: Option[Int], bo: Option[Int]) =
    for {
        a <- ao
        b <- bo
    } yield a + b
Knut Arne Vedaa
  • 15,372
  • 11
  • 48
  • 59
  • This returns `Option[Int]` instead of `Int`. Also, it returns `None` if either `ao` or `bo` is `None`, which is not the desired result. – leedm777 May 16 '12 at 18:29
  • You're right that it doesn't produce the intended result of OP's code, however I'd say it does address the spirit of the question's title. – Knut Arne Vedaa May 16 '12 at 19:17
  • while it doesn't return 0 like the original answer, simply adding getOrElse will do the trick – Jed Wesley-Smith May 16 '12 at 21:56