4

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 ?

MaatDeamon
  • 9,532
  • 9
  • 60
  • 127

1 Answers1

6

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.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Thank you for this detailed explanation well put together. A bit further clarification if i may `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 consume the input / output stream in the effect F.` If i follow well, conceptually by extension it could be said that a Request or Response, are actually IO Action that consume Byte ? – MaatDeamon Jun 10 '21 at 09:54
  • Or it is something (a Data Structure) that requires to run an IO Action to be fully constructed ??? – MaatDeamon Jun 10 '21 at 09:59
  • 2
    The idea is that http4s doesn't have to evaluate the body "in advance" to construct a request/response. For example, http4s passes a `Request` to your service after it parses HTTP headers, but before it receives the whole body (https://http4s.org/v0.21/entity/). If you want to read the body, you "consume" the stream, ie. evaluate it to `F[Array[Byte]]`, `F[String]` or something similar. – Michał Pawlicki Jun 10 '21 at 10:04
  • 2
    @MaatDeamon In the very narrow sense one could say that, but really an HTTP request and response are more than just bytes of body content. They are the headers, query parameters, some request types have no body at all. Together they compose the request and response entities. – Yuval Itzchakov Jun 10 '21 at 10:19