In clojure, I can write something like this:
(defn wrap-my-header
[handler]
(fn [request]
(let [request (if (get-in request [:headers "my-header"])
(assoc request :has-my-header? true)
request)]
(handler request))))
In this middleware, I'm checking if I have a non-nil value in my-header in :headers, if yes, I'll attach some data in the request map. This demonstrates that I can treat request and response as a somewhat "stateful" data.
I'm still new in haskell and wanted to do similar things with scotty. After looking at the type of middleware, I can create a middleware like this:
myMiddleware :: Middleware
myMiddleware app req respond = app req respond
After staring at the type for a long time, I still don't know how to do it. Some reading and thinking makes me assume that this is not possible, Middleware can only only short-circuit the handler and/or alter the generated response. Is this true?
This confused me for a long time too! But figuring it out gave me a helpful technique for understanding Haskell library types.
First I'll start with my middleware being undefined:
So what is
Middleware? The key is to look at the definition of the type:Let's start at the first layer (or level of abstraction) by having the middleware take an Application and return an Application. We don't know how to modify an application, so we'll return exactly what's passed in for now.
But what is an Application? Again, let's turn to Hackage:
An Application is a function! We may not know exactly what each part is supposed to do or be, but we can find out. Let's replace
Applicationin our type signature with the function type:Now we can see this type should allow us to access a
Request! But how do we use it?We can expand
theOriginalAppin the function definition into a lambda expression that matches the return type:We can do whatever we want with the request now:
Now what about that
undefined? Well, we're trying to match our lambda to the type of that return function, which takes a Request and a function (that we don't care about) and returns anIO ResponseReceived.So, we need something that can use
myModifiedRequestand return anIO ResponseReceived. Luckily our type signature indicates thattheOriginalApphas the right type! To make it fit, we only need to give it thesendResponsefunction too.And that's it, that will work! We can improve the readability by simplifying the type annotation back to
Middleware, and getting rid of the lambda. (We can also eta-reduce and remove thesendResponseterm from both the arguments and the definition, but I think it's clearer if it stays.)The result: