Michael
Michael

Reputation: 42050

How to handle Option of List in Scala?

Suppose I have a function getCustomers and getOrdersByCustomer.

def getCustomer():List[Customer] = ...
def getOrdersByCustomer(cust: Customer): List[Order] = ...

Now I can easily define a function getOrdersOfAllCustomers

def getOrdersOfAllCustomers(): List[Order] =
  for(cust <- getCustomer(); order <- getOrderByCustomer(cust)) yield order

So far, so good but what if getCustomer and getOrdersByCustomer return Options of the lists ?

def getCustomer():Option[List[Customer]] = ...
def getOrdersByCustomer(cust: Customer): Option[List[Order]] = ...

Now I would like to implement two different flavors of getOrdersOfAllCustomers():

How would you suggest implement it?

Upvotes: 5

Views: 12176

Answers (3)

Vidya
Vidya

Reputation: 30310

I think you should consider three possibilities--a populated list, an empty list, or an error--and avoid a lot of inelegant testing to figure out which one happened.

So use Try with List:

def getOrdersOfAllCustomers(): Try[List[Order]] = {
  Try(funtionReturningListOfOrders())
}

If all goes well, you will come out with a Success[List[Order]]; if not, Failure[List[Order]].

The beauty of this approach is no matter which happens--a populated list, an empty list, or an error--you can do all the stuff you want with lists. This is because Try is a monad just like Option is. Go ahead and filter, forEach, map, etc. to your heart's content without caring which of those three occurred.

The one thing is that awkward moment when you do have to figure out if success or failure happened. Then use a match expression:

getOrdersOfAllCustomers() match {
  case Success(orders) => println(s"Awww...yeah!")
  case Failure(ex) => println(s"Stupid Scala")
}

Even if you don't go with the Try, I implore you not to treat empty lists different from populated lists.

Upvotes: 4

Kevin Wright
Kevin Wright

Reputation: 49705

This should do it:

def getOrdersOfAllCustomers(): Option[List[Order]] = {
  getCustomer() flatMap { customers =>
    //optOrders is a List[Option[List[Order]]]
    val optOrders = customers map { getOrderByCustomer }

    // Any result must be wrapped in an Option because we're flatMapping 
    // the return from the initial getCustomer call
    if(optOrders contains None) None
    else {
      // map the nested Option[List[Order]]] into List[List[Order]]
      // and flatten into a List[Order]
      // This then gives a List[List[Order]] which can be flattened again
      Some(optOrders.map(_.toList.flatten).flatten)
    }
  }
}

The hard part is handling the case where one of the nested invocations of getOrderByCustomer returns None and bubbling that result back to the outer scope (which is why using empty lists is so much easier)

Upvotes: 2

tiran
tiran

Reputation: 2431

Try this,

def getOrdersOfAllCustomers(): Option[List[Order]] =
  for{
    cust <- getCustomer().toList.flatten; 
    order <- getOrderByCustomer(cust).toList.flatten
  } yield order

Upvotes: 2

Related Questions