How to compose functions that return Option[List] in Scala?

前端 未结 2 1810
我在风中等你
我在风中等你 2021-01-01 07:26

Suppose I have two functions to get orders and order items:

def getOrders(): Option[List[Int]] = ...
def getOrderItems(orderId: Int): Option[List[Int]] = ...
         


        
2条回答
  •  爱一瞬间的悲伤
    2021-01-01 07:34

    You really want to be able to turn the middle two layers of Option[List[Option[List[Int]]]] inside out, so that you can get the options and lists next to each other. This operation is called sequencing, and it's provided by Scalaz:

    import scalaz._, Scalaz._
    
    val items: Option[List[Int]] =
      getOrders.flatMap(_.map(getOrderItems).sequence).map(_.flatten)
    

    You could equivalently use traverse, which combines the map and sequence operations:

    val items: Option[List[Int]] =
      getOrders.flatMap(_ traverse getOrderItems).map(_.flatten)
    

    If you don't want to use Scalaz, you could write your own (less polymorphic) sequence:

    def sequence[A](xs: List[Option[A]]) = xs.foldRight(Some(Nil): Option[List[A]]) {
      case (Some(h), Some(t)) => Some(h :: t)
      case _ => None
    }
    

    And then:

    val items: Option[List[Int]] = getOrders.flatMap(
      orderIds => sequence(orderIds.map(getOrderItems))
    ).map(_.flatten)
    

    The monad transformation solution is actually pretty straightforward as well (if you're willing to use Scalaz):

    val items: Option[List[Int]] = (
      for {
        orderId <- ListT(getOrders)
        itemId  <- ListT(getOrderItems(orderId))
      } yield itemId
    ).underlying
    

    The nice thing about this approach is that you don't have to think about where you need to flatten, sequence, etc.—the plain old monadic operations do exactly what you want.

提交回复
热议问题