Introduction
Adam: Welcome to Corecursive where we bring you discussions with thought leaders in the world of software development. I am Adam, your host.
To me, the promise of functional programming is code that is easier to reason about referentially transparent code wherein if I give it some inputs, I get a defined output back always, and there’s no magical context outside of this. It t makes code easier to understand, easier to test, easier to debug. From a unit testing perspective, you don’t have to mock a bunch of things. You can just provide the inputs, and then measure the outputs at what whatever level of the system it is. It’s just a simple block of code or if it’s a giant web request.
I think, so, it’s easier to think of these examples like a function that returns a Fibonacci value, much harder to imagine a functional referentially transparent system that is doing something complex like a web framework. http4s does the latter. It attempts to and succeeds, I think, at taking these functional concepts and applying them to web frameworks.
Today, I talked to Ross Baker about how this works and the benefits thereof. Ross Baker is the creator of the http4s project. Ross, welcome to the podcast.
Ross: Thank you. I’m glad to be here.
Adam: Is creator a good term, the major contributor? What term do you like?
Ross: I am one of the originators, and I’ve been around the longest, but through the life of the entire project, we’ve had several people contributing to it. So, I push back a little bit on it being my project, but I have been around the longest.
What Is Http4S?
Adam: So, what is http4s?
Ross: So, http4s was created to fill a hole that we saw in the Scala community where you look at other languages, and they all had this abstraction that would let you write multiple web front ends on top of it and bind it to multiple servers. So, you think about Ruby where they’ve got Rack or you’ve got Python where you’ve got WSGI or in a little bit more of the typed FP realm you’ve got Haskell where you’ve got WAI.
There wasn’t anything quite like that in Scala, and we wanted to create something like that where you could say, “All right. We want to take a DSL that looks a little bit like unfiltered or looks like Scalatra or looks like something else entirely and be able to run it on your choice of back end whether it’s a servlet container or on a Netty back end or on other back ends yet to be imagined. We wanted to have that cartesian product so to speak between ways of writing services and what you run the services on. So, http4s was born as the abstraction layer to sit between the two, to fill that gap in the Scala community.
Adam: And when you say to bind to multiple servers, but one at a time?
Ross: Yes, one at a time. So, you’ve got your choice. You can write your http4s. Most people use what’s called the HTTP for sDsl which is based on Scala extractors just pattern matching of requests. That’s how most people are writing their services at this point, and you can write that, and you can take it, and you can run it on Jetty or you can run it on Tomcat or what most people do is they run it on our native Blaze back end.
Adam: You mentioned one of the goals was… Well, I don’t know if you mentioned it as a goal, but you mentioned Netty. So, does it run on Netty?
Ross: There is an open pull request to get it working on Netty. So, originally, on the project, the reason the project started in the first place was a bunch of us had been working on Scalatra. It was a port of Sinatra that is a Ruby framework to Scala, and that was very tightly coupled to the servlet container.
And what we wanted to do was be able to run it on Netty. We had a lot of users who wanted to do that. We thought we could get a little bit more speed out of it, and we were missing abstraction. So, it was a bunch of Scalatra developers early on the project who got that started, and we tried writing a Netty background for it, and the person who was in charge of the Netty back end, he ended up peeling off the project. And somebody else came along. His name is Bryce Anderson, a really great guy. He works at Twitter now.
He wanted to learn how to do NIO. So, he came along and wrote his own back end just to learn NIO. That’s the Blaze back end that grew into the primary one that most http4s users use today. So, Netty was an original goal of the project, and then about five years later, we finally got a pull request where it’s coming to fruition.
Adam: Nice. Maybe, we should define Netty. So, Netty is, to my understanding, it basically is for serving network requests. How would you describe it?
Ross: Yeah. It’s just a good general networking toolkit. It’s t’s built on NIO. You can do non-blocking with it as opposed to some older JVM techniques. It’s kind of the foundation of a lot of networking libraries in the JVM. It surprises people when they come to http4s for the first time. They expect there to be a Netty backend, and we say, “Well, actually, we’ve got this other thing that somebody wrote on his own and completely reinvented things, but it works out really well for us.”
What Is Blaze?
Adam: So, that thing is Blaze. Is that correct?
Ross: That’s Blaze, yes.
Adam: Describe how Blaze differs from Netty, if you could.
Ross: okay. So, Blaze is written in Scala whereas Netty as a Java project. So, A Blaze is pure Scala, but it still differs a little bit from the rest of the http4s code base because Blaze emphasizes futures and ByteBuffers and mutability, whereas http4s is much more on the functional side of things. Blaze is going for raw performance. It gets down really in the muck. So, it uses ByteBuffers directly. It uses Futures There’s mutability happening all over the place. It’s a very different code base from http4s.
Now, an http4s user is not going to have to be exposed to any of that because http4s wraps around that. It provides a pure functional interface on top of it. So, it encapsulates all that, but for speed underneath the covers, Blaze resorts to those tricks. And it ends up being really nice because that way, it provides a reasonable level of abstraction, lets people write code in the functional style with all the benefits thereof, but they get the performance benefits of getting down there and doing the really dirty tricks that the JVM is optimized for. So, the two work together very well, it turns out.
Adam: So, an interesting thing about http4s and Blaze, I mean, an interesting thing, I guess about http4s to me I guess was that it doesn’t use Netty. And then a secondary interesting thing is that when I was looking at benchmarks, there’s and power benchmark. I don’t if you’re familiar with it. It’s like a million web server web frameworks all tested. It does very well. It’s performant.
Ross: Yes. So, raw Blaze is going to do better than http4s on those benchmarks just because when you think about http4s is a layer that sits on top of Blaze. So, http4s is doing all the work that Blaze is doing and more, all these functional wrappers in terms of wrapping bodies in an FS2 stream and adding an immutable object model. That’s nice for programming, but that turns out to not be free.
So, if you compare those benchmarks, you’ll see that Blaze does better than http4s, and we get a lot of questions about that because, oh I see that http4s has so much overhead. And my answer to that is that’s true when you look at the benchmarks, but when you look at real world applications, if you look at the tech and power benchmarks, what they’re doing is they’re doing a ping response or they’re doing the most trivial JSON parsing, and that’s not what most people are doing.
Most people when they’re handling a response, they’re doing a network call. They’re talking to a database. They’re making another web service call. They may be aggregating a web service call with a database. And when you add all that up, it turns out that the overhead that http4s adds to provide that functional API on top of something lower level like Blaze turns out to be insignificant in the grand scheme of things.
Adam: Yeah. I agreed. And if you look like if you compare apples to apples, if you compare http4s, I forget what stats I was looking at, but I was comparing it to the Play framework and to Dropwizard. And it seemed to be doing better than either of them except on the serialization of JSON, but I think that’s unrelated. I think that it’s quite fast as is the thing that I was noting.
Ross: Yeah. My answer to people who are concerned about it is it’s always been fast enough for me, and I’ve used it at three companies now. And it’s never been a problem for me. So, definitely, a goal of the project is to be as fast as possible, but we’re not going to sacrifice correctness or being able to reason about functional programming toward that goal. So, it’s a goal, but it’s a secondary goal.
Embracing Functional Programming
Adam: Let’s talk about that. What is your goal around functional programming and your framework?
Ross: That’s an excellent question. So, if you look at the landscape of web libraries, that tends to be not an area where functional programming has excelled in the past. It’s an area where you see a lot of mutability all over the place. I’ve seen a lot of apps where they have a functional core, and then on the outside, they will use something like Play or Akka HTTP or something like that that isn’t pure.
And what we wanted to do is come up with something that pushed functional programming out further to the edges of the application. Now, at some point, you’re talking to a socket. So, IO is still happening. Every time you’re doing functional programming, there’s still an end of the world. And beyond that end of the world, you’re not functional anymore, but what we wanted to do was see could we get any benefits out of expanding that functional universe. Is there value and having referential transparency and composability up at the web tier layer rather than just at the business logic layer? That was one of the real motivating factors for the project as well.
The FP Challenge
Adam: And how have you found trying to work towards that goal?
Ross: I’ve been very happy with it because there’s always this tension in Scala where you start doing functional programming, you start using things like an IO Monad or a Task. There’s various implementations out there. Cats Effect IO or Monix Task. You use those, and then, you get into some other libraries, and they want to use Scala concurrent Future and trying to adapt between those two worlds.
You can adapt between those two worlds. It’s just a couple lines of code, but you’ve got that fault line in your code when you don’t push the functional boundaries all the way out to the end. So, it’s really nice to have a web library where everything is based on those same functional monads that the business logic tier is based on.
So, we’re polymorphic over our effect. That means that anything that implements the Cats Effect type classes is something that you can run your server in, and that lets you bring your Monix Task or your Cats Effect IO or there’s a ZIO project out there that also implements the type classes. There’s several options out there. And whatever you built your back end on, you’re going to be able to build your web tier on, and you’re not going to have that mismatch between the two base effects anymore. So, being able to have all that line up has been very pleasant to deal with.
Adam: Yeah, to unpack that. So, you’re saying you want the framework to be… You want to follow functional programming principles. So, you want your side effects wrapped in in something, right?
Ross: Right.
Adam: Scale, there’s like several competing somethings which is sometimes attention, right? So, the trick that you’ve used is to abstract over that using Cats Effects. Is that right?
Ross: Correct. Yeah. So, that’s a relatively recent innovation in the project. When we first started it, we were built on top of Scalaz concurrent Task, and then we moved over to FS2 as Scalaz stream gave way to FS2. FS2 had its own Task Monad. So, we started to use that. And around that same time Monix Task was becoming popular, and there were some people that were still off on Scalaz and didn’t want to move.
So, we had all these competing things that became clear that there wasn’t going to be one effect that was going to dominate all the others in the community. And the functional programming community in Scala is small enough as it is to divide things even further. That’s kind of unfortunate. And the Cats Effects project came along providing pipe classes over these various things.
So, it’s got this notion of an Effects-type class. And it’s got some weaker type classes underneath that, but basically, what an effect type class does is it defines things that are able to describe an effect that gets run when you choose to run it, and it’s able to describe asynchronous effects. It’s able to describe things that can be flatMap. It’s a Monad. It’s able to do error handling. It’s a Monad error. It has all these capabilities built into it. It’s got brackets. So, it’s got resource safety built into it.
So, there are a bunch of types that satisfy those constraints for things that we’re looking for up in the web tier. So, by expressing everything in terms of this Effects-type class, we’re able to welcome everybody back under the tent. As long as they’ve got an instance of that type class, they’re able to use http4s without any friction between their back end and their front end.
Too Many IO Monads
Adam: It’s funny in retrospect, I guess, like, “Okay. We want to abstract and encapsulate these side effects.” But everybody wants to abstract and encapsulate them in their own little way. And then so, as a framework builder, I can see how that poses a problem for you.
Ross: Yeah. That was something that was very frustrating to me early on. I just thought, “Okay. An IO Monad is a pretty basic thing. We should all just come to agree on one. Everybody should use that, and we don’t need this.” But there are a little bit different trade-offs on it where if you look at the Cats Effect IO versus the Monix Task, Monix Task has a scheduler that you pass along. And it’s able to do things like fairness because it passes along this context.
But in IO Monad at the Cats Effect IO, it doesn’t need that context passed along, but at the same time, it’s not able to do those automatic thread shifts for fairness. So, you get a little bit of a trade-off there on complexity of the implementation versus some things that it can do for you out of the box. So, there are some pros and cons to those various things as well.
HTTP Requests as Functions
Adam: I wanted to return to your statements about how a functional paradigm makes a lot of sense for a web framework, and I think you’re right. In most web frameworks, they seem to have a bunch of extra stuff. They’re not pure functions that take a response and produce a result, but why not, right? I think if you think of I make a request against a web server, I’m giving it this request, and then all it needs to do is provide a response. Obviously, there is IO happening there, but it seems like a very contained function.
Ross: Exactly. And one area that we find that really shines is for testing. So, people come along and say, “Yeah, we’re really enjoying this library. We got a service up and running. And now, we’re talking about testing it, and we’re wondering what sort of test kit do we need,” because people are used to having like the Akka test kits or I’m showing my age way back here, I’ve been doing servlet programming for a long time. What we used to do to test things is we used to use this library called Cactus which would let you run JUnit tests remotely into a servlet container using a bunch of RMI calls.
So, we’ve come a long way over the years. And what we get down to in http4s when we recognize that a service is just a function, is you don’t need any kind of mock container at all. You don’t need any web server at all. You’ve just got your function that represents a service. You construct a request in your unit test, and then, you assert over the response in your unit test, and you don’t need to mock anything anywhere along the way.
Adam: Yeah. It sounds like a great promise. You can think of it as this input and output requests and response, but I think that, in practicality, it needs to get much more split up than that, right? We may have, like, I pass some sort of JSON object through like a form post, and then it gets like converted to something else, and then that gets processed, and then, maybe, we need to reach out to the database and provide an update some row, and then return a results.
When we have all these different steps, how do we compose that in http4s?
Ross: Yeah. One thing that I like to encourage people to do is just remember that everything is a function. So, the definition of a service, it’s a kleisli arrow you’ve got some effect type F that can be your IO or your task or whatever, and then, you’re going from a request to a response. And if you think about that, we can unwrap the kleisli. We can set that aside for a second. That’s just a glorified wrapper around a request to an F of response.
And those functions compose. So, instead of having a request to an F of a response, what you can do is you can have a request to an F of an A. And what that represents is parsing some type A out of your request, and then you’ve got your business logic which is A to F of B which represents an asynchronous function from an A to your response type D, and then you’ve got a function of B to F of response which is how you take a B and serialize it back into a response, and you take all those three functions I just described for request to F of A, and then A to F of B, and then B to F of response, and you can compose all those back together, and you get back to that original shape of request to F of response.
So, you can decompose your logic however you like. And when you look at that with your A to F of B, there isn’t anything related to HTTP inside of that. So, that should reflect the shape of your business logic.
Adam: So, it lets you not have to worry about these wire level concerns in your business logic or in your serialization logic.
Ross: Right, just like we want to push the networking level out to the edge of the world. What http4s does with its functional interfaces, it lets you push the HTTP concerns out to not the fullest edge of the world, but the next layer beyond that, the next layer of the world. HTTP can live inside of there, and you can just keep getting closer and closer until you get down to your core of your business logic, and that’s just a simple function from A to F of B.
404s, Combining Services and Middleware
Adam: So, if this HTTP application is a function from a request to a response in some effect, what happens if it’s like a 404? The function’s not total. There’s no route that matches this request. How do we model that?
Ross: So, one thing that you can do is, this is hard coded in the current production version of http4s. We’re loosening this constraint to enable some more things, but right now, if you look at the definition, it’s actually a kleisli of option T over F,? to request to response. And what that option T does is that’s a Monad transformer. That lets you embed an option inside an effect.
And what that lets you do is it lets you respond asynchronously and decide can I handle this request or do I need to pass this request onto someone else. So, you either return an F of some response or you return an F of none. And what we get out of that option T is you’ve got a semi group K over that option T and with that semi group K, what that enables us to do is you can take two services, and you’ve got this neat little operator in Cats. So, it’s combined K, the symbolic version of it is less than plus greater than.
You can take two services and glue them together like that. So, you’ve got one service that tries to handle the request. And if it can’t handle the request, it will delegate onto the other service that you have combined K on to it. And if none of the services that you’ve chained together in that way work, then, you end up the back end knows how to handle an F of none, and it knows to turn that into a 404 response.
Adam: There’s a lot there, but I think I follow. So, a service, so it returns to none in this option T, and then, you’re saying the service as a whole is a semi-group. So, then, semi groups expose like concatenation. So, you concatenate together these services. So, that’s how you can combine services, and then, if you still have a none, then you have some sort of handler that returns to your 404. Did I get it exactly right?
Ross: Exactly. Yeah. So, what we were describing before with the request to F of A and A to F of B and B to F of response, that’s a way of decomposing services, and then, what we just talked about is a way of combining services, and then we’ve got a third way that you can stack things up. We’ve got this concept of middlewares in http4s. And what middlewares do is that’s a function of one service to another service.
So, that’s a way of doing, I like to think of that as vertical composition whereas what we just described is horizontal combination. But with vertical composition, what you can do is you can take a service, and you can change your request on the inbound side of the service or you can mutate the response on the outbound side to the service, and that enables you to layer extra concerns on so you don’t need to handle it every route inside of your service, but build up these other things.
The term middleware that’s borrowed, I think, from Rack is the first place I’ve seen that in the Ruby community, and that lets you add on concerns like GZIP compression. So, you can check the headers on the way in for what’s accepted. And if it’s acceptable to GZIP the response, you can GZIP the response on the way out, and then your service doesn’t need to worry about that, or authentication is a middleware where you take a service, and then you wrap some authentication logic around it and make sure that the service only gets invoked if the user is authorized to use it.
Adam: The middleware is applied to all routes, I guess. Is that correct?
Ross: Correct. Yeah. A middleware is a function of one service to another service. And, yeah, it just wraps around. If you’re familiar with Java Servlet Programming, it’s like a servlet filter. Other libraries will call those interceptors. There’s various terms for it. You find this concept over and over again way back in the history of web frameworks. We just call it middleware because we are pretty React and WSGI inspired, but it’s the same concept.
Adam: And the reason it takes a service and returns of service is because it’s just wrapping it and putting some extra functionality inside, and then calling it.
Ross: Exactly yeah, and that ends up being totally transparent to the service that you’re wrapping. And it ends up being transparent to the back end. The service is just the contract layer between the people who are implementing their business logic and the people who are implementing the back ends to run the business logic.
Adam: I think it’s pretty neat. I imagine I want to talk to you a little bit about people learning this framework, but I think that if you know the concepts, it seems to come apart nicely, right? You’re saying, “Oh, to write a route, you just do pattern matching.” Your service just has some case classes, and then like to combine these services. Well, it’s a semi group, and you just concatenate them together, and then to do a filter style thing. You just take one service and emit a new one that wraps it. The concepts seem very clean.
Learning FP Concepts
Ross: Yeah. That’s definitely been a design goal is what we want to do is we want to use Scala concept and well-known FP concepts wherever they apply. So, the idea of combining two things together, we could have come up with some ad hoc way of combining two services together, but if you look to the functional programming community for inspiration, how do you combine two things? Well, you’ve got a semi group. That’s a way of combining things.
Now, ours happens to be higher that we’ve got that type parameter in there. So, it’s a semi group K rather than a semi group, but the same basic concept. That’s something that was already laying around. So, rather than inventing new syntax, we’re able to take something that you can use to combine things that have nothing to do with http, and we’re able to reuse that vocabulary.
So, anybody who’s already immersed in this, they understand that. Now, not a lot of people understand semi group K coming in. So, that’s something that we’ve taught to other people, but that ends up being cool too because then, when they see semi-group K outside of http4s, they recognize that these things all start to click together once you get immersed in this community, or as you were saying with pattern matching, that’s something that you don’t even really need to be into functional programming to do.
The reason we like the HTTP for SDSL, is if you understand how case classes work and how extractors work, you can get a pretty firm grip on how to match requests and do your request routing. We’re just using basic Scala concepts for that. Anywhere we could reuse something where it’s going to interoperate well with what people already understand. That’s something that we’ve strived to do.
Adam: Yeah. It’s interesting. One thing is that maybe you’re bringing people up to speed on these concepts, but these concepts aren’t unique to your framework. I know in the play framework, I may get this wrong, but they have action filters, and I’m pretty sure their action filters are just like kleisli, but they call them something different. They just call them action filters, but the methods map. They have like [inaudible 00:26:17].
Ross: Yes. That’s something that we’ve gone back and forth on a lot in terms of what is the value of type aliases, and we tried to hide some of those things behind type aliases. So, we would use the real functional types underneath the covers. We would alias things. So, we weren’t afraid to use a kleisli, but maybe we didn’t want to call it a kleisli. Maybe, we wanted to put an alias on that.
And a little bit of feedback we’ve gotten from the community is it’s easier to just learn the common term for these things than it is to learn your aliases for them. Even if you’re using the right types under the covers, those aliases add confusion. So, there are a few aliases that we’ve deprecated over the years. We’ve still got some, but there’s some that we’ve deprecated over the years for just that reason where people are actually pretty capable of learning that vocabulary and want to because that’s the same thing that they can use elsewhere.
Learning Lazyiness
Adam: What have you found are the learning difficulties of people coming to this library?
Ross: I think one of the things that’s been a hard leap for people is they’re used to Future, and then they see these IO types, and they’re pretty similar in terms of, yeah, they model asynchronicity. They’re a Monad. So, I can flatMap those. People come and do it with that sort of understanding, but the idea of eagerness versus laziness, that’s one thing that’s tripped people up a little bit, it’s they’re getting up to speed on the idea that they’re describing this computation, but it isn’t running yet in the same way that the Future is.
If I had to say what’s the biggest difference between an IO and a Future , t’s that when you construct a Future, it starts executing right away. It’s already running in the background whereas when you construct an IO, that’s not going to run until you tell it to run. People run into some surprises with that, and they don’t understand, well, why don’t you have a binding for Future? You can bring all these other things. You can bring an IO or a Task. Why can’t you bring a Future?
Well, some of the tricks that http4s uses are built on top of that laziness and having the back end in control of what’s running. If we want to get really deep into the weeds here, the key distinction is that Future can’t have a Sync instance because it can’t delay its effects, and that’s something that we need to do in http4s.
Adam: It can’t have a Sync instance. Describe Sync.
Ross: Sync is one of the type classes that comes from Cats Effect. It’s one of the super classes of the effect type class that I keep talking about. And Sync is something. It extends Monad. It extends a couple other things. One of the big operations that it adds is it has a way of delaying a computation, and that notion of delaying a computation doesn’t exist in Future. Once you construct a Future, it’s already running.
And a good example of why that hurts in http4s is let’s go back to that example of combining services with the combined K operation where you execute one service, and then if it falls through, you execute another service. Well, if you look at Future, what happens is you’ve got to run both sides of that. So, you run one side of it. You get your IO. You run another side of it. You get your IO, and then you look at those IOs.
To combine those IOs, you need to have both IOs, and then you look inside of that first IO, and then, if you’ve got a sum on that, you don’t need the value of the second IO. So, you don’t actually need to execute that, but you need it to construct that IO. That’s the problem that we run into when we use Future is if you’re trying to use Future as your base Monad, you invoke one service, and you get a Future, and you invoke another service, and you get a Future.
Let’s say that the first service responds, and when you look at that first service responds so you don’t need the value of that second service, but that second service has gone ahead and executed its effect anyway. And, maybe, that effect is to execute a lead operation that mutates your database when that request should have never run. So, that’s what we get out of laziness. You’re able to build up these constructs using laziness that you can’t do with Future.
Adam: So, that’s a funny example. So, for instance, it could be just matching routes, and the first service matches on something, but then, if that first service wasn’t there, it would fall through to some delete, and that’s going to get executed anyways because if in fact it wasn’t a Future, is that right?
Ross: Right. So, delete is an extreme example. Ideally, you don’t have a delete as your follow-through case. That’s a little bit of a contrived example, but you can think of things that do hurt where it’s an expensive call. So, maybe, you’ve got one service out in front that operates off of a cash, and then, if you don’t have a cash value of it, that you fall through, and you do the expensive call.
Well, if you try to combine those two services together, that’s a very natural way of combining two services. If you do that with Future in this architecture, what happens is you call the [inaudible 00:31:01], and then you call the underlying value, and the underlying value is computed even if you have the Cats value because you’ve returned a Future from each of those that you’re trying to combine together.
So, in terms of doing destructive updates, that’s probably not so much a real world concern, but in terms of doing something expensive, doing more work than you need to do, and that gets the idea of being lazy. We call it lazy for a reason that you don’t do any more work than you have to do.
On Scala’s Futures
Adam: Future is just a bad effect to some, maybe. Do you agree or-
Ross: Future is not ideal for functional programming. Let’s put it that way. There’s been a lot of debates over Future over the years. So, if you’re not doing functional programming, Future has some really nice aspects to it. So, Future is really good at fairness with its execution context. It’s good at memoizing values.
So, you refer to a Future, and you refer to it multiple times, and you don’t have to recalculate that value. So, there are some nice things about Future, but none of those things are really nice once you enter the functional programming realm. Memoization, if you’re not thinking in terms of FP, you just want to avoid repeating work.
When you think in terms of FP, you want referential transparency. So, the memorization, that can be good or bad depending on your perspective and what you’re trying to accomplish.
Why Not Moniods
Adam: I think we’re deep in the weeds of like FP concepts. So, I think we’ve already lost anybody that doesn’t know these terms. So, I might as well just ask it. You said before you can combine services because they’re a semi-group. Why aren’t they a monoid? Can you have an empty service, and then-
Ross: They could be a monoid, but then what you have to have is you have to have a zero value. So, what would be the zero value for a response that would respect all the monoid laws. That’s something that ends up being a little bit tricky. So, a knee-jerk reaction to that is your zero value might be a 404 response.
Adam: Yeah. That’s what I was thinking. Your 404 could be your empty case, and then-
Ross: Okay. And now, let’s talk about combining things with identity laws and so forth. So, if you have a 404 as your base case, is your 404 because you have actively asserted that there is nothing there or just because you’re saying this is something that I don’t know how to handle.
So, maybe you’ve got something where you have handled the request, and you want to absolutely say, “There is nothing here. There should be nothing here. I don’t want any other services consulted. I want this to be a 404.”
If you use a 404 as your zero, and you try to combine things, if a service tries to say, “I am asserting that nothing is here, I insist on returning a 404,” well, when you combine that, the combination operator is going to look into that and say, “Oh, a 404, if it’s a 404, I’m going to try to consult this other service anyway.”
Maybe, that’s okay maybe it’s not. I guess it depends on your perspective on things.
Adam: Oh, because, yeah, if a service doesn’t match anything, you want to go to the next service.
Ross: Right. When you treat things as a monoid where you’re looking at a response with a zero rather than a monoid K where you’re looking at two effects and seeing whether you’re combining those irrespective of the value type they’re in, that’s where you run into trouble. It’s the idea of an implicit 404 because the request fell through versus an explicit 404 which is something that is really meaningful.
Adam: Yeah. I believe I understand the complexities there.
Ross: We used to have this idea of a special follow-through response. This was terrible, and I’m glad that we got rid of it. We used to have a monoid rather than a semi-group K. And the monoid, we had a special response which was something that would return a 404, and then to treat it as the 404 that fell through versus a 404 that doesn’t fall through, we would look at that by reference equality and see, oh, this is the magic 404.
And that was just a very ad hoc concept that confused a lot of people. That was not one of our better design decisions. So, that was something that was stripped out in favor of the architecture that we have now.
Adam: So, the answer is we tried your idea. And it was horrible, and we’re glad we got rid of it.
Ross: Yes.
Adam: That’s funny. I saw this talk that you did that began with the statement HTTP applications are just kleisli functions from the streaming request to the polymorphic effect of a streaming response. So, what’s the problem, which is hilarious. How about streaming? How does how does streaming work?
Streaming Requests
Ross: So, streaming is something that we have built in from the very beginning. We’ve talked a lot in terms of FP and in terms of making sure that we’re not running our effects too soon and how you’re able to asynchronously generate a response, but when you look at what a response is and a request is, it goes deeper than that because you don’t just have a single atomic chunk as a request or a response.
You have these bodies, and these bodies can be very large. They can be as large as gigabytes, and they arrive over the network not all at once. You don’t just typically get a gigabyte snap your fingers and it’s all there. It’s something that streams in. So, what we wanted to do was we wanted to from the very beginning of the library come up with a streaming approach to handling request bodies and response bodies.
So, if you look at our request at our response definition, you’ll see that the body is defined as an FS2 stream of bytes, and that’s something that fits very nicely into this functional ecosystem as well. FS2 is functional streaming for Scala. That’s what the name means. I guess it’s FS squared, I’m assuming that, but functional streaming for Scala.
And you’re able to consume these streams in a functional way. So, you take a stream, and then you compile it down to an effect. So, when you want to decode a body, what you do is you take a stream, and then you compile it. And then, you’re able to fold that down. So, you get an F of A. When we talked before about how you take a request and turn it into an F of A, the way that you do that and parsing the body is you take the FS2 operations which takes a stream of F to byte. And it’s able to return you an F of A through its various operations.
That’s how you do the decoding. And then, likewise, you can encode things. So, you’ve got your B, and you can turn that into a stream of F of byte, and you can put that into your response wrapping around the status and any headers that you need to add, and you can encode things out that way.
Adam: Nice. So we’re using FS2 as the kind of way to get to get streaming.
Ross: Correct. Yeah. And that ends up being very nice because if you look at the Servlet API, the only way that you can consume a body that way is you’ve got Java IO. It’s got this listener interface on it so you can do non-blocking, but it’s very complicated. The typical way of doing that is you use the old java.io, and that’s a blocking interface.
There isn’t anything functional about it. You’re mutating the stream as you go along whereas by using FS2 streams, that lets us in the same spirit as the rest of the application. It lets you describe the computation. You’re describing how you’re going to decode it, and then at the end of the world, everything compiles down into the F effect and the back end knows how to run that. So, it fits in very well as far as that goes.
Adam: Yeah. It’s definitely a clean way of structuring things that you come up with. I’m still surprised that you aren’t saying though that the difficulty we have is people getting up to speed on just the conceptual, terminology, I guess, associated with the project. That hasn’t been a problem like streams, kleisli, semi group K.
Ross: I think people embrace the vocabulary pretty well. It’s the concepts of laziness and not mutating all over the place. That tends to be the bigger leap that people need to make. In terms of learning new words, people absorb new words for things pretty easily. There’s a lot of handwriting in the FP community that we have these scary words for things. Instead of just calling it an effectful function, we call it a kleisli or instead of just calling it combinable, we call it semigroup K.
These are these words that we worry are putting off people, and people who aren’t immersed in functional programming, they’re intimidated by these words. So, maybe that worry is true to some degree, but as people get in there and try to grasp the concepts, I think they have more trouble with the concepts themselves than the naming of the concepts.
Adam:
When Not to Use Http4S?
Yeah. I suppose that makes sense. So, when is http4s not appropriate? Is there cases where I shouldn’t be using it as my web framework [inaudible 00:40:03]?
Ross: Excellent question. So, I would say that the JVM is too heavy for a lot of services. So, those ping routes, if you look at the tech and power benchmarks, you’ll see that the JVM frameworks tend not to be at the top of the very simple things. If you’ve got very simple needs where you’re not going to benefit from this scalability of complexity that you get with functional programming or you’re not going to benefit from having a long-running server process, it really warms up and has well-tuned garbage collection, and things like that.
If you’re just doing very trivial things, then I think you don’t want to be on the JVM altogether, and you might want to look elsewhere. If you are committed to the JVM areas, you might want to look elsewhere. If you’re not committed to learning the functional concepts, if you are happy with Futures and don’t want to wrap your Futures in IO, if you want to be more in the mainstream, if you want to have your back end be built on top of Akka where you’re just asking actors, and you don’t want to learn any of these other things that wrap around it, then, yeah, maybe http4s is not for you in that case.
There’s definitely a learning curve there. You have to buy into functional programming, and then you start to try to pick up other things off the shelf, and you find that they’re built on Futures or they’re built in immutable style, and maybe you need to write a little light wrapper around that.
Now, I personally think the benefits of that tend to pay off for those wrappers. So, I’m in favor of that. I’m in favor of teaching people that and a team that’s willing to learn that. I think they can be very successful with http4s. A team that doesn’t want to climb that learning curve would probably be better off looking elsewhere.
Adam: Yeah, and I think the unit testing alone, like I guess there’s more overhead perhaps to learning some new concepts, but the amount of time that is just spent writing unit tests or just trying to test things that are not a function, that have all these contexts and mocking and seems like you could pay off that learning very quickly.
Ross: Yeah. I think testing is one area that people end up being absolutely delighted. Another one is we’ve got a nice relationship between the server and the client in terms of if you want to provide a mock client, what you can do is you can take the HTTP service interface, and you can do client from HTTP service. So, you can return your [inaudible 00:42:26] responses in there.
And then if you need to test a client, you’ve just got that client based on that service that you can control or if you want to do a proxy server, you’re able to take a client, and you can make that to an HTTP service where anything that comes in, you just delegate the requests, and you run them through the client. There’s that relationship because everything is built on top of that basic foundation of a request to an F of a response.
There’s a close relationship between a client and a server where you’ve got good benefits on the testing end going one direction or you’ve got benefits on the proxy and going the other direction.
What’s Next?
Adam: So, where do you see http4s going? Does it still have things to accomplish and new features?
Ross: Absolutely. So, we would like to continue to add more back ends. We’ve got that Netty back end of the pipeline. Jose Cardona is working on that one. Christopher Davenport is working on a pure FS2 TCP base, and that would eliminate one of our dependencies. And it would be streams all the way down.
So, we’ve got a couple more back ends that we would like to roll out. One thing that I would like to see more of is I’d like to see more libraries written on top of it. So, a lot of people have associated http4s with its DSLS. That’s the case class extractors that you see all over the place. That’s a really good way of writing services, but it has some limitations in terms of trying to come up with appropriate responses. If you’ve got a route that matches certain methods, but it doesn’t match the method that you have requested, that’s something in pure HTTP.
You’re supposed to return a 405 response, but we don’t have a way of saying, “Well, this request almost matched if you issued with one of these methods that would have matched,” but we don’t support this method. There’s a special response that you’re supposed to return in an API for that. The case class extractor DLS doesn’t work very well for that, but there is something out there http4s directives which lets you compose services in a different way, and that’s able to handle it.
That one is inspired by the Akka directives. So, that’s an alternative way of writing services that comes with its own set of trade-offs, or we’ve got another library out there, Raw, which is a really cool library. It uses a bunch of implicit tricks to capture context about the function, and it knows what response types can be brought in, how you’re decoding things. And it’s able to compile your service definition down into a Swagger definition.
So, you’ve got API documentation that’s machine generated. So, that’s three different ways that we have right now of writing services, and I would like to see even more ways of writing services. So, I hope to see more of those emerge over time as well.
Adam: Yeah. So, I would like to see the reverse. So, start from open API spec and generate the scaffolding of the service.
Ross: That’s interesting. There is a project. I think that’s Andy Miller has a project out there where you take it, and we can generate an http4s client off of that. I don’t think we have anything doing the server scaffolding, but that’s a very good idea as well.
Adam: Yeah or both ways. It might be a strange request, but it tends to be like if we’re building some little service or something that we start with an API, and we just use Swagger to say like, “This is what it might look like,” because I think APIs, like, your interfaces are important. So, sometimes, we start there. And once you’ve started there, I mean, I guess, generating the other way becomes less useful.
Ross: Yeah. The problem if you’re generating that server scaffold is how do you keep it up to date as you start to implement those endpoints, and then you add something else into your Swagger definition. How do you merge the scaffold of the new endpoints with the implemented endpoints you already have? Keeping that in sync can be a challenge.
Adam: No. That’s definitely true. And yeah. And translations, the type system of Swagger is not the richest. So, I think there’d be some translation challenges there, but that’s my two cents.
Ross: Yeah. That would be a fun project. So, I guess one other thing that I’d like to see in the future of http4s is we would really like to steer it toward a 1.0, and I’m a little bit embarrassed because the project has been out there since I believe 2013 is when we started the project. Here we are halfway through 2018, and we’re still on version 0.X. And a reason for that is there’s been a lot of churn in the functional Scala community in terms of what libraries that we use.
Our project is older than Cats itself. It’s certainly older than Cats Effect. We’ve had some churn in the foundation libraries that we use. And now, we’re getting to a point where Cats is at 1.0. Cats Effect has its 1.0 release candidates. That’s coming very soon. There’s a branch for FS2 1.0. So, those are our three real core foundational libraries. Those are finally committed to, yes, this is something that we’re going to do long-term support on. We finally have a stable API, and that’s removed our excuse for not having a long-term stable API.
So, once those all hit 1.0, that’s something that we need to get very serious about as well and make sure that people can build on it with confidence.
Library Churn
Adam: Has that been a source of difficulty for you just the dependency churn, I guess, like, the dependencies you’re using changing?
Ross: Yes. So, dependency churn when those libraries change beneath us, that’s been the primary motivator for us doing a breaking release, and we’re able to fix a lot of our own things. So, it gives us an excuse to fix a lot of things that we didn’t like about ourselves as well, but that’s been the motivating factor for when we do those breaking releases as that churn underneath us.
We started on Scalaz and Scalaz Stream, and then we migrated as Scalaz Steam, moved over to FS2. We were trying to support both Scalaz and Cats together, and that was just too much maintenance. So, we ended up dropping Scalaz support. If you want to use Scalaz, you still can. There’s the Shims library for that by Daniel Spiewak. So, there’s a pretty good story if that’s the way you go, but we were able to just standardize on Cats and Cats Effect.
And now, we’re not trying to maintain multiple branches at once. That’s really been a blessing for our development because we’ve got a lot of contributors, but still we’d rather be developing HTTP rather than developing compatibility layers and doing merges.
Remote Work
Adam: I can understand that. One thing I wanted to ask you about was how you find working as a remote developer.
Ross: Let’s see. This is my fourth job working remotely now, and I absolutely love it. It’s been just a really good fit just for family reasons being able to greet the kids as they come off the business, give my wife the freedom. She can go get an office job. Her job doesn’t relocate as well as mine does. So, that way, I’m able to be the stay-at-home parent, but also the working parent. So, that’s been really good.
And I’m able to expand out and work with people that I wouldn’t get to work with otherwise. I live in Indianapolis which is a great town. I love it here, but we’ve got a relatively small tech community, and I’m involved in the local meetups, and that’s great, but being able to work with Californians and work with people elsewhere around the world on a daily basis, that’s been something that’s been really good living in one of these smaller tech towns as well.
Adam: Yeah. I think it’s the Future. This is my third job as a remote developer, and I live in a small town as well. And I remember I think the end of the first week when I started my first job of working from home, and I remember thinking like, “I don’t think I’m ever going back and do an office.”
Ross: Right. Yeah. It’s just great because we’re all enthusiastic about FP, and FP is growing by leaps and bounds, but it’s still a tiny percentage of the jobs out there. So, when you’re in a small town and you’re working in a niche technology, and then there are certain things that you like to focus on within that niche, you’re in a niche of a niche of a niche. And at that point, the geography becomes a constraint that’s really nice to shed.
Adam: Definitely. Definitely. Well thank you so much, Ross, for taking the time to talk with me. Great project, and I hope you and the people working on it continue the great work.
Ross: Thank you. I appreciate the invite, really enjoyed the interview.