HI everyone,
say i have these two functions
val getUser: UserId => Future[User] = ???
val getAddress: User => Option[Address] = ???
Is there anyway i can compose them to get? flatMap
won’t work because the signature does not fit.
val getUserAddress: UserId => Option[Address]
What about something like this:
val getUserAdress: UserId => Future[Adress] = userId => {
getUser(userId).map(getAddress).flatMap {
case Some(address) => Future.successful(address)
case None => Future.failed(UserNotFound)
}
}
1 Like
I was actually thinking more in a generic sense. Is there any combinator that allows one to compose such functions without having to deconstruct. Something like how map
and flatMap
does it.
No, you can not mix two different arbitrary monads.
You either need a way to transform one into another, or keep the nesting, e.g. Future[Option[A]]
1 Like
Thank you. I was just curious is there a way to do it and i don’t know about it.
Cats has the Nested
datatype which makes it easier to work with nested effects.
1 Like
Yeah but it stop at Applicative once you want to flatMap
you need an specific monad transformer.
Anyways, I bleiev OP’s point is not how to deal with nested effects, but it if was a common function for something like:
def flatMapG[F[_], G[_], A, B](fa: F[A])(f: A => G[B]): F[B]
Which AFAIK there isn’t (in the general sense). But well, I am actually not that versed on cats; or category theory overall.
In scalaz terminology, a conversion between different F[_]
s and G[_]
s is called a NaturalTransformation.
https://www.javadoc.io/doc/org.scalaz/scalaz_2.13/7.3.1/scalaz/NaturalTransformation.html
Monads don’t compose in general (and this is what you need because you’re composing effectful functions end to end), but in this case you can compose them with the OptionT
monad transformer, basically by massaging both result types into Future[Option[A]]
and then wrapping up in OptionT
to yield a composed effect type OptionT[Future, A]
. This type is a monad and you can compose with flatMap
or a for
comp.
If you have questions about this stuff get on Cats gitter and plenty of people there can help.
import cats._
import cats.data._
import cats.implicits._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
val getUser: UserId => Future[User] = ...
val getAddress: User => Option[Address] = ...
def getUser2(uid: UserId): OptionT[Future, User] = OptionT.liftF(getUser(uid))
def getAddress2(usr: User): OptionT[Future, Address] = OptionT.fromOption(getAddress(usr))
def composed(uid: UserId): OptionT[Future, Address] =
for {
u <- getUser2(uid)
a <- getAddress2(u)
} yield a
composed(...).value // Future[Option[Address]]
3 Likes
@laiboonh here is a very simple example of using OptionT that you may want to use as a stepping stone to warm up on OptionT before looking at @tpolecat’s example, which is the definitive answer to your question:
import cats.data.OptionT
import cats.implicits._
import org.specs2.concurrent.ExecutionEnv
import org.specs2.execute.Result
import org.specs2.mutable.Specification
import scala.concurrent.Future
class Temp(implicit ee: ExecutionEnv) extends Specification {
"OptionT" should {
"allow me to treat Future[Option[Int]] as a monad" in test
}
def test: Result = {
val futureMaybeX : Future[Option[Int]] = Future.successful(Some(3))
val futureMaybeY : Future[Option[Int]] = Future.successful(Some(5))
val futureMaybeZ : OptionT[Future, Int] =
for {
x <- OptionT(futureMaybeX)
y <- OptionT(futureMaybeY)
} yield x + y
futureMaybeZ.value must beSome(8).await
}
}
1 Like
@laiboonh if you want to know more about monad transformers, I found the following video useful: Options in Futures, how to unsuck them.
In fact, I found it so useful that I made this: https://www.slideshare.net/pjschwarz/monad-transformers-part-1 (download for intended best quality)
1 Like
Thank you everyone. You guys are awesome