Salahzar Stenvaag
Salahzar Stenvaag

Reputation: 23

Scala Programming: Canonical way to convert a sequence of if else into a flow of map

What is the preferred canonical method in Scala (or FP) of adapting complex chained if...then... statements? For example (metacode):

def fetchLastOrderFor(CustomerId:UUID)= {
    fetch Customer data from an Id
    if Customer exists 
       fetch extra information for that customer (address?)
       fetch latest order of that customer
       if Order exists
          fetch OrderDetails
          ....
}

So far I'm modeling this as a sequence of nested match...case Some...case None returning something like Either[MotivationOfMissingOrderReturned,OrderWithAllDetails]

But I'm unsure if this is the best approach (it looks to me a bit ugly and difficult to read) or maybe I can model it with some kind of chained monad flow like Try or map / filter, Futures, maybe using elegant for comprehensions.

I'm new in FP and trying to grasp how to methodically convert my non-functional instinct into FP jargon.

I know that FP tends to avoid using if... then ... else in favour of collective actions made on containers like Option, List and other monads.

Upvotes: 0

Views: 128

Answers (2)

Ryan
Ryan

Reputation: 7257

If you use Option, you can put that all together as a for-comprehension.

def getCustomer(id: UUID): Option[Customer] = ???
def getCustomerInfo(customer: Customer): Option[CustomerInfo] = ???
def latestOrderForCustomer(customer: Customer): Option[Order]
def getOrderDetails(order: Order): Option[OrderDetails] = ???

def fetchLastOrderFor(customerId:UUID): Option[OrderDetails] = {
  for {
    customer     <- getCustomer(customerId)
    info         <- getCustomerInfo(customer)
    latestOrder  <- latestOrderForCustomer(customer)
    orderDetails <- getOrderDetails(order)
  } yield {
    orderDetails
  }
}

Upvotes: 1

Justin Pihony
Justin Pihony

Reputation: 67135

One way is pattern matching:

def fetchLastOrderFor(CustomerId:UUID)= {
customer = fetch Customer data from an Id
customer match{
  case Some(custData) => {
    info = fetch extra information for that customer (address?)
    lastOrder = fetch latest order of that customer
    lastOrder match{
      case Some(orderData) => ...
      case None => return invalid 
    }
  }
  case None => return invalid
}

Another is through a for-comprehension

def fetchLastOrderFor(CustomerId:UUID)= {
for{
  customer <- fetch Customer data from an Id
  info <- fetch extra information for that customer (address?)
  lastOrder <- fetch latest order of that customer
} yield { ...return some combination of the data above... }

Which really boils down to a bunch of flatMaps

So, it depends on your preference. I often feel that pattern matching or for comprehensions are more readable to a larger audience of developers, but it depends on the situation.

But, ultimately, most of this relies on using the right data structures. IE. the above example fits best with Option, Disjunction, or Validation.

Upvotes: 2

Related Questions