Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I convert this foldLeft : Double expression to use Option[Double] instead?

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!

like image 365
Lunivore Avatar asked Sep 01 '10 09:09

Lunivore


2 Answers

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.

like image 184
sepp2k Avatar answered Sep 21 '22 11:09

sepp2k


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!

like image 30
olle kullberg Avatar answered Sep 19 '22 11:09

olle kullberg