scalafunctional-programminghttp4s

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


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 ?


Solution

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