What is the intent behind `F[Something[F]]`?

Viewed 270

I see a lot of F[Request[F]], F[Monitor[F]] and F[Something[F]] around. Although I understand the mechanics of how to handle this, I don't have an intuitive understanding of why some effect (F) should appear like this (both wrapping around Something and appearing inside that Something). Insights ?

2 Answers

A simple HTTP server can be represented with the following function: Request => Response

However, we commonly need to perform an effectful operation such as retrieving data from database or call external service. Such cases we would define like this: Request => F[Response]

http4s has own abstraction over Routes (Endpoints) called HttpRoutes[F] and it is type alias for Kleisli[OptionT[F, ?], Request, Response] as mentioned in the documentation.

As we know from cats documentation, Kleisli is just compositions of functions that return a monadic value like Option[A], Either[L, R], etc.

One of the most useful properties of functions is that they compose. That is, given a function A => B and a function B => C, we can combine them to create a new function A => C.

In this case http4s's Kleislei takes Request and returns F[Response], simply Request => F[Response]. F is an effect type.

With a bit of modification the above Request and Response types, we get the following:

type HttpRoutes[F] = Kleisli[OptionT[F, *], Request[F], Response[F]]

An HttpRoutes[F] is declared as a simple series of case statements. Each case statement attempts to match and optionally extract from an incoming Request[F]. The code associated with the first matching case is used to generate a F[Response[F]].

The simplest case statement matches all requests without extracting anything. The right hand side of the request must return a F[Response[F]].

We can see here simple demonstration of F[Response[F]]

scala> val getRoot = Request[IO](Method.GET, uri"/")
getRoot: org.http4s.Request[cats.effect.IO] = Request(method=GET, uri=/, headers=Headers())

scala> val io = service.orNotFound.run(getRoot)
io: cats.effect.IO[org.http4s.Response[cats.effect.IO]] = <function1>

Here cats.effect.IO[org.http4s.Response[cats.effect.IO]] is unresolved yet, this is aync result. It will be resolved in future at some point as simple Future/Promise. And we can enforce it to run:

scala> val response = io.unsafeRunSync
response: org.http4s.Response[cats.effect.IO] = Response(status=200, headers=Headers())
  1. Kleisli
  2. Http4s
  3. Practical FP in Scala by Gabriel Volpe

F[_] is just an abstraction. It can be List or IO of something else with kind * -> *. The main idea that you can define common functions. A good example of this a Functor typeclass and the map function inside this class. So in your code, you can say that you F is a functor (F[_]: Functor) and after it allows you to use a map function.

Related