Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binary operator with Option arguments

Tags:

scala

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)

like image 709
JasonMond Avatar asked May 16 '12 11:05

JasonMond


2 Answers

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
like image 53
oxbow_lakes Avatar answered Nov 27 '22 06:11

oxbow_lakes


def addOpts(xs: Option[Int]*) = xs.flatten.sum

This will work for any number of inputs.

like image 40
Luigi Plinge Avatar answered Nov 27 '22 04:11

Luigi Plinge