Search code examples
scalafunctional-programmingscala-catscats-effecttagless-final

Conversion between Option[F[ShoppingCart]] to F[Option[ShoppingCart]]


I have the following algebra in Scala (I am using the Tagless Final Pattern):

trait ShoppingCarts[F[_]] {
  def create(id: String): F[Unit]
  def find(id: String): F[Option[ShoppingCart]]
  def add(sc: ShoppingCart, product: Product): F[ShoppingCart]
}

Using the above algebra, I created the following program:

def createAndToCart[F[_] : Monad : ShoppingCarts](product: Product, cartId: String): F[Option[ShoppingCart]] =
  for {
    _ <- ShoppingCarts[F].create(cartId)
    maybeSc <- ShoppingCarts[F].find(cartId)
    maybeNewScF = maybeSc.map(sc => ShoppingCarts[F].add(sc, product))
    maybeNewSc <- maybeNewScF match {
      case Some(d) => d.map(s1 => Option.apply(s1))
      case _ => Monad[F].pure(Option.empty[ShoppingCart])
    }
  } yield maybeNewSc

I don't like the code very much in the for-comprehension construct that converts an Option[F[ShoppingCart]] into an F[Option[ShoppingCart]]. I know for sure that I can do better, but I don't know how to improve it.

I am using Cats.


Solution

  • You are looking for traverse and sequence. What these functions do is "switch" the order of effects, so they are able to change G[F[A]] to F[G[A]] if G has an instance of Applicative. Option has such an instance in scope, so you'd be able to use it.

    traverse takes additional mapping function, but if you just want to "switch" effect, so sequence will be way to go:

    for {
          _ <- ShoppingCarts[F].create(cartId)
          maybeSc <- ShoppingCarts[F].find(cartId)
          maybeNewSc <- maybeSc.map(sc => ShoppingCarts[F].add(sc, product)).sequence //here you need to use sequence
    } yield maybeNewSc
    

    or you can merge map and sequence into one step with traverse:

    for {
          _ <- ShoppingCarts[F].create(cartId)
          maybeSc <- ShoppingCarts[F].find(cartId)
          maybeNewSc <- maybeSc.traverse(sc => ShoppingCarts[F].add(sc, product))
    } yield maybeNewSc
    

    You can read more on sequence and traverse in cats docs.

    Another thing I would suggest you is checking out are monad transformers since they're making dealing with nested monads stacks like F[Option[A]] a lot easier.