HOME | EDIT | RSS | INDEX | ABOUT | GITHUB

Finch vs Http4s, which is FPer

This is purely personal overall comparation from Functional Programming point of view, which only consider composible, extensible, and joy of FP. Performance and eco system are out of the scope.

The explicit version that I'm comparing here is finch 0.26.1 and Http4s 0.20-M4

Definition:

Scale from 1 to 5:

Finch

composible: 🍎 🍎 🍎 🍎

finch takes full advantage of shapeless' product and coproduct to routes Matcher

val getApples = get("apple") { Ok("🍎🍎")} 
val getApple = get("apple" :: path[Int]) { (id: Int) => Ok("🍎")}

val routes = getApples :+: getApple

:: here is a product, means the path will match if there is an "apple" and an Int in the path path[Int]

:+: constructs a coproduct, which means either getApples or getApple matched route will match

About Middleware, finch need to mix finagle filter to do the job. so it will end up very ugly and inconsistent from the routes above

class AuthFilter(implicit ttf: ToTwitterFuture[ProgramF]) extends SimpleFilter[Request, Response] {
  def apply(req: Request, service: Service[Request, Response]): Future[Response] = {
    (req.path, req.headerMap.get("TOKEN")) match {
      case ("/login", _)        => service(req)
      case (url, Some(token)) => ??? // fetch user info and pass to service

and then compose filter and service

new AuthFilter andThen routes.toServiceAs[Application.Json]

you may have notice that the finangle filter is more lower level verbose code and inconsistent with the previous shapeless DSL.

extensible: 🍎 🍎

finch has a lot of internal custom types, e.g. the magic happen behind the DSL above:

val getApples = get("apple") { Ok("🍎🍎")} 

is:

  1. string "apple" will implicitly convert into Endpoint[Unit]
  2. get("apple") will covert Endpoint[Unit] into function Mapper[F, In] => Endpoint[F, Out]

basically just Cont, taking Mapper as parameter and wrapping itself.

  1. Another implicit conversion will take place in Ok(""), from Output[String] => Mapper[F, String]

If you didn't follow, that's fine, because there is so many implicit conversion and internal custom types took place here.

But think about it, because all implicit conversion was well-defined in finch internally we've restricted to the types they provided.

For example my controller will actually return me a Free Monad i.e. Free[Program, Output[String]]

How can I get it to work with the router?

val getApples = get("apple") { Free[Program, Output[String]].pure(Ok("🍎🍎"))} 

it won't compile because the implicit conversion expecting an Output[_] type

actually it only supports IO[_], Output[_], Response

UPDATE: from 0.27.0 it supports custom converter to convert any user custom type to IO

implicit val conv = new ToEffect[Free[Program, ?], IO] {
def apply[A](a: Free[Program, A]): IO[A] = ???
}
val getApples = get("apple") { Free[Program, Output[String]].pure(Ok("🍎🍎"))} 

But still, it's so many implicits magic happen behind and the journey wasn't so fun.

joy of FP: 🍎 🍎 🍎

except some funs from shapeless there aren't actually any cats interop during composition of routes

While that could be much more attractive to beginner friendly though, since user don't have to have any knowledge about cats before using finch.

Http4s

composible: 🍎 🍎 🍎 🍎 🍎

meanwhile, http4s takes advantage of cats to achieve path Matcher

val getApples = HttpRoutes.of[IO] {
  case GET -> Root / "apple" => Ok("🍎🍎")
}
val getApple = HttpRoutes.of[IO] {
  case GET -> Root / "apple" / id => Ok("🍎")
}

val routes = getApples <+> getApple

what happen here is HttpRoutes.of[IO] {...} return a data type Kleisli{OptionT{F, ?}, Request{F}, F{Response{F}}}

<+> is combindK of SemigroupK[Kleisli[OptionT[F, ?], Request[F], ?]]

You may already realize that nothing of above make any sense to you if you aren't familiar with data types and typeclasses defined in cats or scalaz.

It requires some knowledge background from cats, just like you should know some sort of shapeless to fully understand what the hell is :: and :+: about.

Short story, Kleisli is a generic data type representing a function A => F[B]

so you could imagine that Kleisli[OptionT[F, ?], Request[F], F[Response[F]]] is just something like Request[F] => OptionT[F, F[Response[F]]]

while <+> is very like :+:, it combines these Kleisli, but any of these match, it will return that matched Kleisli

About Middleware, it's nothing more just compose a Kleisli of type Kleisli[Option[F,?], Request[F], F[Request[F]]] before or compose a Kleisli of type Kleisli[Option[F,?], Response[F], F[Response[F]]] after

e.g. auth user before getApple

def auth = Kleisli { req: Request[IO] =>
  findUserInDatabase(req) match {
    case true => OptionT(IO(Some(req)))
    case false => OptionT.fromOption[IO](None)
  }
}

auth andThen getApple

extensible: 🍎 🍎 🍎 🍎 🍎

Since route matcher is simply just Kleisli, extending http4s to support types other than F[Response[F]] will be much simpler.

For the same example as above in finch, that my controller will return a free program Free[Program, IO[Response[IO]]] instead of IO[Response[IO]]

// type alias for route that return free monad
type FreeRoute[F[_]] =
  Kleisli[OptionT[F, ?], Request[F], ProgramF[F[Response[F]]]]

// custom dsl
def route[F[_]: Monad](
  pf: PartialFunction[Request[F], ProgramF[F[Response[F]]]]): FreeRoute[F] =
  Kleisli(
    (req: Request[F]) => OptionT(implicitly[Monad[F]].pure(pf.lift(req))))

val getApples = route {
  case GET -> Root / "apple" => Ok("🍎🍎").pure[Free[Program,?]]
}

since getApples is still Kleisli, all the methods such as <+> are still available

to hook it back to http4s route, simply map interpreter to getApples

val interp: Program ~> IO = ???

val router = getApples flatMapF interp

here I used flatMapF because interp will give it another IO, which need to be flatten

joy of FP: 🍎 🍎 🍎 🍎 🍎

Since Http4s takes full power of data type Kleisli, since Kleisli at the end is just a function, it's much more composable and extensible in every way.

Once you have some knowledge around cats data types and typeclasses, you'll be able to enable all cats power in http4s for free. Since Kleisli has instances for:

  • Functor
  • Applicative
  • Monad
  • Alternative
  • Choice
  • Arrow
  • Parallel
  • Monoid
  • MonoidK

cats.svg

As you can see it already cover most of the cats typeclasses, plus, those typeclasses are almost the most popular typeclasses in FP and cats.

And you will get all the chance to practiceall thee fun in your http4s server by just using Kleisli data type.