5

I've got some code that returns an IO but I need a Effect in http4s.

import cats.effect.{Effect, IO}

class Service[F[_]: Effect] extends Http4sDsl[F] {
    val service: HttpService[F] = {
        HttpService[F] {
            case GET -> Root =>
                val data: IO[String] = getData()
                data.map(d => Ok(d))
        }
    }
}

gives

[error]  found   : cats.effect.IO[F[org.http4s.Response[F]]]
[error]  required: F[org.http4s.Response[F]]
[error]         data.map(d => Ok(d))
[error]                 ^
Stephen
  • 4,228
  • 4
  • 29
  • 40
  • Is there any reason why `F` isn't bounded to `IO` here? Meaning `HttpService[IO]`? – Yuval Itzchakov Apr 21 '18 at 12:58
  • This is how the http4s template does it. It does work if like `HttpService[IO]`. Im assuming its possible to keep it abstract and better some how. Maybe not.? – Stephen Apr 21 '18 at 13:06

1 Answers1

5

One way we can get around using a concrete IO[A] is using LiftIO[F]:

class Service[F[_]: Effect] extends Http4sDsl[F] {
  val service: HttpService[F] = {
    HttpService[F] {
      case GET -> Root =>
        getData().liftIO[F].flatMap(Ok(_))
    }
  }
}

LiftIO lifts will lift: IO[A] => F[A], but this yields us F[F[Response[F]. In order to get things to compile, we'll flatten on F since it has a Monad (or FlatMap) instance in cats due to our Effect context bounds requirement.

If we want more detail, this is the -Xprint:typer result:

cats.implicits.catsSyntaxFlatten[F, org.http4s.Response[F]](
   cats.effect.LiftIO.apply[F](Service.this.evidence$1)
                     .liftIO[F[org.http4s.Response[F]]](
                       data.map[F[org.http4s.Response[F]]](
                        ((d: String) => Service.this.http4sOkSyntax(Service.this.Ok)
                          .apply[String](d)(Service.this.evidence$1,
                                            Service.this.stringEncoder[F](
                                              Service.this.evidence$1, Service.this.stringEncoder$default$2[F]))))))(Service.this.evidence$1).flatten(Service.this.evidence$1)

And at the end of the world when you want to give a concrete effect, for example Service[IO], we get:

val serv: Service[cats.effect.IO] = 
  new Service[cats.effect.IO]()(effect.this.IO.ioConcurrentEffect)

Where ioConcurrentEffect is the Effect[IO] instance.

It seems that all the good stuff is defined at org.http4s.syntax.AllSyntax trait.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321