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.
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.