Composing functions involving different effects

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