# Purpose of Network.Wai.ResponseReceived

**URL:** <https://discourse.haskell.org/t/purpose-of-network-wai-responsereceived/14904>\
**Category:** Learn\
**Created:** [October 27, 2026, 11:44pm UTC](https://discourse.haskell.org/t/purpose-of-network-wai-responsereceived/14904 "2026-10-27T23:44:29Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![rini](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/rini/32/5799_2.png) [@rini](https://discourse.haskell.org/u/rini)\
**Post date:** [October 27, 2026, 11:44pm UTC](https://discourse.haskell.org/t/purpose-of-network-wai-responsereceived/14904/1 "2026-10-27T23:44:30Z")

</div>

Specifically, the [documentation](https://hackage-content.haskell.org/package/wai-3.2.5/docs/Network-Wai.html#t:ResponseReceived) states “This is to avoid the need for Rank2Types in the definition of Application.”

I do vaguely remember why the Application type cannot be simply `Request -> IO Response`, the explanation being in a talk about `wai`, but first thing that comes to my mind is changing the type into this:

```haskell
type Application = forall r. Request -> (Response -> IO r) -> IO r

```

Without enabling any extensions, this works on GHC 9.10.3. But seemingly using `NoRankNTypes` makes it not work, so is that what is being suggested? From the documentation of that extension, `Rank2Types` used to mean a different restriction, and I also can’t see why this type isn’t a rank-1 type.

when writing this post I did not realise `RankNTypes` was on by default so my question changed a bit

Would or could a newer version of `wai` use this? Is there a reason to avoid the extension with it being default? I want to design a generic API for Discord gateway bots, so `wai` has come to my mind with it

---

<div class="post-metadata">

**Author:** ![jackdk](https://avatars.discourse-cdn.com/v4/letter/j/9f8e36/32.png) [@jackdk](https://discourse.haskell.org/u/jackdk)\
**Post date:** [October 28, 2026, 12:35am UTC](https://discourse.haskell.org/t/purpose-of-network-wai-responsereceived/14904/2 "2026-10-28T00:35:21Z")

</div>

If designing from scratch in 2026, I imagine it would be less controversial to use the higher-rank type. `type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived` was first defined in `wai-3.0.0`, released in 2014. Strangely, `Network.Wai` enables `{-# LANGUAGE Rank2Types #-}` but the authors appear to have decided on exposing a simpler interface, so it could not have been chosen to broaden support across Haskell compilers. Even the ancient GHC 6.8 (2007) supports [arbitrary-rank polymorphism](https://downloads.haskell.org/~ghc/6.8.1/docs/html/users_guide/other-type-extensions.html#universal-quantification). So the feature was mature and known to `wai`’s authors. The only reason I can think of is that a simpler interface was chosen to make it easier for users to follow.

---

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [October 28, 2026, 11:03am UTC](https://discourse.haskell.org/t/purpose-of-network-wai-responsereceived/14904/3 "2026-10-28T11:03:17Z")

</div>

> [@rini](#):
>
> From the documentation of that extension, `Rank2Types` used to mean a different restriction, and I also can’t see why this type isn’t a rank-1 type.

You’re technically right and the type `forall r. Request -> (Response -> IO r) -> IO r` is itself not a higher rank type. However, there are a few nuances:

1. GHC used to only allow the keyword `forall` if `RankNTypes` (or `Rank2Types`) was enabled. Nowadays you can use `ExplicitForAll` and write a function signature like this:

```haskell
foo :: forall r. Request -> (Response -> IO r) -> IO r

```

2. Even so, using `forall` in a type synonym is still syntactically disallowed by GHC even with `ExplicitForAll`; it requires `RankNTypes`.
3. Even if GHC would allow defining the type synonym, most uses of it would be a higher rank type. For example, `Application -> Foo` would then expand to `(forall r. Request -> (Response -> IO r) -> IO r) -> Foo` which is a higher rank type. Technically, types like `Foo -> Application` are not really higher rank because the quantifier appears only to the right of the function arrow, but GHC rejects these anyway probably because of historical reasons mentioned in point 1.

---

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [October 28, 2026, 11:10am UTC](https://discourse.haskell.org/t/purpose-of-network-wai-responsereceived/14904/4 "2026-10-28T11:10:47Z")

</div>

> [@jackdk](#):
>
> The only reason I can think of is that a simpler interface was chosen to make it easier for users to follow.

Type inference for higher rank types is undecidable. GHC is pretty good nowadays with `ImpredicativeTypes` but it is still complicated, and you will probably run into unexpected limitations.

For example the `DeepSubsumption` extension is a slight change to the type system which had bigger real world implications than anticipated. See the [simplify subsumption](https://ghc-proposals.readthedocs.io/en/latest/proposals/0287-simplify-subsumption.html) proposal for examples.

And a clear limitation is that you can’t do things like `[Application]` (putting applications in a list) if `Application` is a type synonym with a `forall` in it, unless you also enable the `ImpredicativeTypes` extension.

---

<div class="post-metadata">

**Author:** ![andremarianiello](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/andremarianiello/32/4870_2.png) [@andremarianiello](https://discourse.haskell.org/u/andremarianiello)\
**Post date:** [October 28, 2026, 12:13pm UTC](https://discourse.haskell.org/t/purpose-of-network-wai-responsereceived/14904/5 "2026-10-28T12:13:16Z")

</div>

> And a clear limitation is that you can’t do things like [Application] (putting applications in a list) if Application is a **newtype** with a forall in it, unless you also enable the ImpredicativeTypes extension.

Did you mean “type synonym” here?

---

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [October 28, 2026, 12:19pm UTC](https://discourse.haskell.org/t/purpose-of-network-wai-responsereceived/14904/6 "2026-10-28T12:19:42Z")

</div>

Thank, that was a kind of Freudian slip because I think newtypes are the best solution to deal with these cases. You can define a newtype with a `forall` in it:

```haskell
newtype Application = MkApplication { serve :: forall a. Request -> (Response -> IO a) -> IO a }

```

This has much fewer issues with type inference and type checking in my experience. The cost is that you now need to wrap and unwrap this newtype, but ideally you’d hide that under a nice clean API.

---

<div class="post-metadata">

**Author:** ![andremarianiello](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/andremarianiello/32/4870_2.png) [@andremarianiello](https://discourse.haskell.org/u/andremarianiello)\
**Post date:** [October 28, 2026, 12:25pm UTC](https://discourse.haskell.org/t/purpose-of-network-wai-responsereceived/14904/7 "2026-10-28T12:25:04Z")

</div>

And lens does something similar with its Reified wrappers

---

<div class="post-metadata">

**Author:** ![rini](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/rini/32/5799_2.png) [@rini](https://discourse.haskell.org/u/rini)\
**Post date:** [October 28, 2026, 1:18pm UTC](https://discourse.haskell.org/t/purpose-of-network-wai-responsereceived/14904/8 "2026-10-28T13:18:37Z")

</div>

> [@jaror](#):
>
> 1. Even if GHC would allow defining the type synonym, most uses of it would be a higher rank type. For example, `Application -> Foo` would then expand to `(forall r. Request -> (Response -> IO r) -> IO r) -> Foo` which is a higher rank type. Technically, types like `Foo -> Application` are not really higher rank because the quantifier appears only to the right of the function arrow, but GHC rejects these anyway probably because of historical reasons mentioned in point 1.

That does explain my confusion I think. `Middleware` being `Application -> Application` would clearly turn into a rank-2 type

Speaking of, poking wai directly leads me to write:

```haskell
middlewares app = foldl' (flip ($)) app $
  [ middleware1
  , middleware2
  , middleware3
  ]

```

…which, GHC really does not like when `Middleware` is rank-2

---

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [October 28, 2026, 3:48pm UTC](https://discourse.haskell.org/t/purpose-of-network-wai-responsereceived/14904/9 "2026-10-28T15:48:25Z")

</div>

Yeah, you _can_ make that code work if you enable `ImpredicativeTypes` and add two type applications:

```haskell
middlewares app = foldl' @[] @Application (flip ($)) app $
  [ middleware1
  , middleware2
  , middleware3
  ]

```

[Haskell Playground](https://play.haskell.org/saved/5vARJrbk)

But it is not ideal if you have to explain this to your users (and I didn’t get this right in one go).
