Search code examples
scalascala-catscats-effectdoobie

For comprehension that has to handle 2 optional values and return a Option[T]


The below code works fine, but as you can see the 2nd clause in the for comprehension has a call that is unsafe.

case class ProductView(product: Product, stores: List[Store], warehosue: Option[Warehosue])

def loadView(...): ConnectionIO[Option[ProductView]] = 
for {
   product <- getProductById(id)  // ConnectionIO[Option[Product]]
   warehouse <- getWarehouse(product.get.warehouseId.get.id) // ConnectionIO[Option[Warehouse]]
   stores <- loadStores(...)     // ConnectionIO[List[Store]]
} yield product map { p =>
    ProductView(p, stores, warehouse)   
}

I tried to make that a safe call, but my types don't seem to line up.

   warehouse <- getWarehouse(product.get.warehouseId.get.id) 

How can I improve this, if any of the options is a None, I just want to return a None. This is suppose to return a Option[Warehouse]

I tried this:

warehouse <- product.map(p => p.warehouseId.map(id => getWarehouse(id)))

Hoping someone can help with this part of my for comprehension.


Solution

  • The easiest way is using OptionT and flatTraverse

    def loadView(id: Int):
      ConnectionIO[Option[ProductView]] =
        (for {
          product <- OptionT(getProductById(id))
          warehouse <- OptionT.liftF(product.warehouseId.flatTraverse(getWarehouse))
          stores <- OptionT.liftF(loadStores(...))
        } yield ProductView(product, stores, warehouse)).value
    

    Also alternative variant without OptionT

      def loadView(id: Int): ConnectionIO[Option[ProductView]] = {
        getProductById(id).flatMap {
          _.traverse { product =>
            for {
              warehouse <- product.warehouseId.flatTraverse(getWarehouse)
              stores <- loadStores(...)
            } yield ProductView(product, stores, warehouse)
          }
        }
      }