Need clarification for confusing Http4s Message Type `Response[F]` / `Request[F]`

Viewed 221

I have a hard time understanding why Request and Response are parameterized in F.

Taking something similar is the cats effect datatype Resource.

From the documentation

https://typelevel.org/cats-effect/docs/std/resource

We find the following definition

object Resource {
  def make[F[_], A](acquire: F[A])(release: A => F[Unit]): Resource[F, A]

  def eval[F[_], A](fa: F[A]): Resource[F, A]
}

abstract class Resource[F, A] {
  def use[B](f: A => F[B]): F[B]
}

in particular

def use[B](f: A => F[B]): F[B] makes it clear why Resource is parameterized in F.

Given that there nothing in the documentation that explain Response[F] (please note that i understand very well why F[Response], it is the inner F that i don't graps), i looked a bit into the code https://github.com/http4s/http4s/blob/main/core/src/main/scala/org/http4s/Message.scala

unless i have not looked hard enough i could not find anything that justify the presence of the Effect Type.

Can someone explain the inner F parameter.

In a similar fashion as in https://www.haskellforall.com/2013/06/the-resource-applicative.html

A Resource is an IO action which acquires some resource of type a and also returns a finalizer of type IO () that releases the resource. You can think of the a as a Handle, but it can really be anything which can be acquired or released, like a Socket or AMQP Connection.

Can we have a conceptual definition of what is a response and what it does, that indeed require it to be parameterize on a specific effect Type ?

1 Answers

Let's see the definition for Http[F, G], which is at the core of http4s:

/** A kleisli with a [[Request]] input and a [[Response]] output.  This type
  * is useful for writing middleware that are polymorphic over the return
  * type F.
  *
  * @tparam F the effect type in which the [[Response]] is returned
  * @tparam G the effect type of the [[Request]] and [[Response]] bodies
  */
type Http[F[_], G[_]] = Kleisli[F, Request[G], Response[G]]

Kleisli is essentially a wrapper around an effectful function: A => F[B]:

final case class Kleisli[F[_], -A, B](run: A => F[B])

If we develop the type tetris here, we see that the actual type signature for Http is:

Request[G] => F[Response[G]]

Now, the reason that Request and Response are parameterized in G is that they may contain a body. We see this from both definitions:

final class Request[F[_]](
    val method: Method = Method.GET,
    val uri: Uri = Uri(path = "/"),
    val httpVersion: HttpVersion = HttpVersion.`HTTP/1.1`,
    val headers: Headers = Headers.empty,
    val body: EntityBody[F] = EmptyBody,
    val attributes: Vault = Vault.empty

final case class Response[F[_]](
    status: Status = Status.Ok,
    httpVersion: HttpVersion = HttpVersion.`HTTP/1.1`,
    headers: Headers = Headers.empty,
    body: EntityBody[F] = EmptyBody,
    attributes: Vault = Vault.empty)
    extends Message[F] {

You can see the F is used for the EntityBody[F], which is itself a type alias for a Stream[F, Byte] which is used to lazily consume the input / output stream in the effect F.

It is the case specifically for HttpRoutes[F] that both type parameters are actually the same:

type HttpRoutes[F[_]] = Http[OptionT[F, *], F]

Which is really:

Request[F] => F[Option[Response[[F]]]

Hence the reason we see F[Response[F]] everywhere instead of having a separate type parameter body.

To sum this up, the outter F in F[Response[G]] is used to capture the fact that producing a response may be an effectful operation. This is why F is usually an IO type of some sorts (cats-effect IO, ZIO[R, E, A], etc), and the inner G in the request/response are used to model a stream that produces bytes in the given effect.

Related