views:

254

answers:

9

Can anyone help this Scala newbie? Previously, we summed a number of quantities in a list of entities with those quantities with:

sum = entities.foldLeft(0.0)(_ + _.quantity)

Now the quantity is an Option[Double], and so is the sum. How can I convert this using idiomatic Scala?

If any entity's quantity is None then the sum should also be None. Otherwise the sum should be Some(total).

Edit: Putting this thing into a unit test so that I can try all your answers out. Please note that I do need the result to be None if any quantity is None, because missing quantities mean we haven't finished yet, so the total should reflect this. Even if you don't get the right answer, if you help lead me or others to it, or help me learn something new, I'll upvote.

Edit: @sepp2k wins for a working solution plus explanation. Thanks to all for the learning!

+2  A: 

Ok, this does what you want, I think. (Previous answer misread your requirements).

entities.find(_.quantity == None) match {
  case Some(_) => None
  case None => Some(entities.map(_.quantity).flatten.reduceLeft(_ + _))
}

I think the other answer is more "idiomatic", but this is a lot easier to understand in my opinion.

Eric Bowman - abstracto -
He said "If any entity's quantity is None then the sum should also be None.", so I'm pretty sure he wants the result to be Some only if all the entities are Sum.
sepp2k
What sepp2k said, though I understand why my 2 tests don't match your expectation. The reason for this is that if we haven't got all the quantities yet then I would like the total to show that we're still missing something. BTW, I'm a She, not a He, though I understand why that also doesn't match the expectation ;)
Lunivore
I can't get sepp2k's answer to compile. What is the type of entities?I created a little container class:case class Foo(quantity: Option[Double])...then made entities of type List[Foo]. sepp2k's answer doesn't compile because Foo does not implement map.
Eric Bowman - abstracto -
Ok, I see ... sepp2k's code should be:entities.map(_.quantity).foldLeft(Some(0.0):Option[Double]) { (acco, xo) => acco.flatMap(acc => xo.map(_ + acc))}...that's a very nice solution, with lots of meat around how Option, map, and flatMap work together.
Eric Bowman - abstracto -
This is great; I think though that some of the Java bods might find that switch between Some and None a bit confusing. Thank you for the help and learning!
Lunivore
+7  A: 

You can use Option's flatMap and map methods to combine two Options, so that the result will be Some(f(x,y)) if the two Options are Some(x) and Some(y) or None otherwise.

entities.foldLeft(Some(0.0):Option[Double]) {
    (acco, x) => acco.flatMap(acc => x.quantity.map(_ + acc))
}

Edit in response to your comments:

Here's an example usage:

scala> case class Foo(quantity:Option[Double]) {}
defined class Foo
scala> val entities: List[Foo] = List(Foo(Some(2.0)), Foo(Some(1.0)), Foo(None))
scala> entities.foldLeft(Some(0.0):Option[Double]) {
    (acco, x) => acco.flatMap(acc => x.quantity.map(_ + acc))
}
res0: Option[Double] = None

scala> val entities: List[Foo] = List(Foo(Some(2.0)), Foo(Some(1.0)))                                  
scala> entities.foldLeft(Some(0.0):Option[Double]) {
    (acco, x) => acco.flatMap(acc => x.quantity.map(_ + acc))
}
res1: Option[Double] = Some(3.0)

So yes, it will return None if any of the entities are None.

Regarding map and flatMap:

map takes a function f of type A => B and returns Some(f(x)) for Some(x) and None for None.

xo.flatMap(f), where f is a function of type A => Option[B] and xo is an Option[A], returns Some(y) iff xo is Some(x) and f(x) is Some(y). In all other cases (i.e. if xo is None or f(x) is None) it returns None.

So the expression acco.flatMap(acc => x.quantity.map(_ + acc)) returns y + acc iff x.quantity is Some(y) and acco is Some(acc). If one or both of x.quantity and acco are None, the result will be none. Since this is inside a fold that means that for the next iteration the value of acco will also be None and thus the end result will be None.

sepp2k
That looks like what I want. If you don't mind I'll wait a couple days to see if I get a prettier answer (or one that scares my fellow Scala newbies less!), and in the meantime look up how flatMap and map work. I can see how _ works here too, though how Scala knows the difference between _ and _ and just _ is beyond me...
Lunivore
Could you please confirm that this will give me None if any entity's quantity is None? If we haven't got all the quantities yet then I would like the total to show that we're still missing something.
Lunivore
@Lunivore: I expanded my answer a bit to address your questions. While doing so, I also noticed that my answer didn't quite match the question (in your question quantity is an option, in my answer the object that held the quantity was an option). This is now fixed.
sepp2k
Thank you very much for the explanation. You win.
Lunivore
+2  A: 

EDIT: as the entity is also an optional value code is adapted to that

While @sepp2k's answer is right if you have an Option[Entity] with a Double quantity field what you need should be the following:

entities.foldLeft(Option(0d)) {
  (sum, oe) => for {
    s <- sum
    e <- oe
    q <- e.quantity
  } yield s + q
}

The for-comprehension inside the closure is equivalent to a flatMap/map like in @sepp2k's answer but is easier to read for beginners in my experience.

Moritz
The quantity field is also an Option[Entity]; please see my edit. You get an upvote for teaching me the for / yield syntax anyway.
Lunivore
I am still not sure if this is what you are looking for. Do you mean you have a `List[Option[Entity]]` and entity has a field `quantity` of type `Option[Double]`? If yes, this should work for you.
Moritz
I have a List[Entity] with a field quantity of Option[Double], and I would like an Option[Double] to be returned. The Entity is not an Option.
Lunivore
+3  A: 

I like to use for when working with Option:

// ========= Setup ===============
case class Entity(x: Double){
  // Dummy
  def quantity = if (x < 2) None
    else Some(x)
}

val entities = List(Entity(1), Entity(5), Entity(7))

// ========= Calculate ===============
val quantities = for{
   entity <- entities
   q <- entity.quantity
} yield q

val qSum = quantities.sum

This should be easy for Java people to follow..

(Sorry for the implementation of Entity, I had a hard time to come up with a quantity() implementation that actually returned None at some points.)

EDIT: Added explanation

What you wanted was to calculate the sum, right? With this solution, if quantity() returns None for all entities in the list then the sum will be 0. Why? Because the quantities collection holds no elements.

When using Option with for you can remove all None elements from the resulting list in a very nice way. It is the line:

 q <- entity.quantity 

..that actually removes all None results from the resulting list and extract the Double from the Some(x). So:

yield q

.. will only return Double types. This gives you the opportunity to use the sum() function on the resulting collection, since the collection holds Double instead of Option[Double]. The sum operation is very readable!

olle kullberg
This looks like it returns a Double, rather than Some(Double) or None depending on whether there is any entity without a quantity.
Lunivore
Ok, I will add an explanation to the response..
olle kullberg
Ah. Please see my explanation for why I need None instead of Some(0). Think of it like an order you're totalling up, where some of the prices haven't been decided yet. Having it sum 0 for those would be bad for us as the shopkeeper, because we'd lose money. Better that we know there's still some order prices outstanding!
Lunivore
+1  A: 

I would explicitly check for the absence of None's using

entities.forall(_.quantity.isDefined)

For example:

scala> case class Entity(quantity: Option[Double])
defined class Entity

scala> val entities = List(Entity(Some(10.0)), Entity(None), Entity(Some(15.0)))
entities: List[Entity] = List(Entity(Some(10.0)), Entity(None), Entity(Some(15.0)))

scala> if (entities.forall(_.quantity.isDefined)) {
     |   Some(entities.flatMap(_.quantity).reduceLeft(_+_))
     | } else None
res6: Option[Double] = None
Synesso
Upvoted just for the reminder that there is an interpreter and I can try this stuff out easily.
Lunivore
+1  A: 
val sum = entities.foldLeft(Some(0.0):Option[Double]){
  (s,e) => if (s.isEmpty || e.quantity.isEmpty) None else Some(s.sum + e.quantity.sum)}

or

val sum = if(entities.exists(_.quantity.isEmpty)) None
          else Some(entities.flatMap(_.quantity).sum)
Landei
+1  A: 

This is the same answer as sepp2k/Moritz, but separated into two functions to make things clearer.

def addOptionDouble(optionalA: Option[Double], optionalB: Option[Double]): Option[Double] =
  for {
    a <- optionalA
    b <- optionalB
  } yield a + b

def sumQuantitiesOfEntities(entities: Traversable[Entity]): Option[Double] = 
  entities.foldLeft(Option(0.0)) {
    (acc, entity) => addOptionDouble(acc, entity.quantity)
  }
Daniel
+2  A: 

Many existing solutions work (and the accepted one is canonical, and is what I would normally use), but here's one that is more efficient if hitting a None is common; it short-circuits the evaluation when it hits the first None. Note that this is tail-recursive.

// Replace Option[Double] by your entity type, and it.next with it.next.quantity
def total(it: Iterator[Option[Double]], zero: Double = 0.0): Option[Double] = {
  if (it.hasNext) {
    it.next match {
      case Some(x) => total(it,zero+x)
      case None => None
    }
  }
  else Some(zero)
}
// To use: total(entities.iterator)
Rex Kerr
Thanks - I'll bear it in mind if we have performance hits. Thanks also for increasing my confidence in the answer I took; I'll know to do it a similar way the next time I do something like this.
Lunivore
+2  A: 

"Idiomatic" is amazingly apt because this is called "idiomatic function application", i.e. "lifting" a function into an "idiom" (more modernly: "applicative functor").

In Scalaz, this can be done as follows:

import scalaz._
import Scalaz._

val add: (Int, Int) => Int = (x, y) => x + y
val addOptions: (Option[Int], Option[Int]) => Option[Int] = add.lift

Or like this:

List(1,2,3).map(some(_)).foldLeft(some(0))(add.lift)
Apocalisp