dave
11/08/2017, 11:20 AM(HttpHandler) -> HttpHandler
with the then
methods being extension functions. This seemed cleaner, as HttpHandler was also only a typealias. Unfortunately, when you came to define a Filter, you'd need to explicitly declare the various types:
typealias Filter = (HttpHandler) -> HttpHandler
val myFilter = { next: HttpHandler ->
{ req: Request -> next(req) }
}
This was mildly annoying (and I'm not sure it was the only reason), so we changed Filter to be an interface which extended (HttpHandler) -> HttpHandler
and added an invoke() method to take the function:
val myFilter2 = Filter { next ->
{ next(it) }
}
Overall we thought this read better.
Onto the Filter objects
- these were originally simple functions that existed at the top level (ie. not nested inside another object). However, we noticed that trying to refer to those top-level functions didn't automatically work in IntelliJ for:
1. Using auto-complete - it currently seems to work with "ctrl+space" if the function is declared in your local project, but not from external JARs. So within the http4k project itself it would be fine, but within an app which uses the http4k JARs, "ctrl+space" would not work. This was somewhat mitigated by grouping the filters under parent object
instances - such as ServerFilters
and ClientFilters
.
2. Navigation to the source of a filter - both using "open class" command and switching between an implementation and the related unit test. I realise that you can use "symbol search" for this in IntelliJ - but there is a lot of noise in the list since everything is listed and you inevitabily have to type/scroll more than you need 🙂
To solve this, we had 2 options:
1. Convert each of the functions to (nested) classes:
class OverrideContent(private val message: String) : Filter {
override fun invoke(p1: HttpHandler) = { p1(it).body(message) }
}
We started with this but it bugged because:
a. the requirement to define all the inputs as private val
- this gets pretty tedious.
b. the name of parameter "p1" in the Handler - and Intellij bugs you if it's not p1
. We haven't found a way to name this parameter that works.
c. the type of that class is forever OverrideContent
and not Filter
- eg. If I've got an instance of OverrideContent
and I want to add it as a parameter to a function, IntelliJ automatically chooses to type the parameter as OverrideContent
and not Filter
. OverrideContent
has no other methods or state to access - I can't think of a reason why you'd ever want to refer to it as anything other than as a Filter
.
2. Convert each of the functions to nested `objects`:
object OverrideContent {
operator fun invoke(message: String) = Filter { next ->
{ next(it).body(message) }
}
}
This achieved the same as the class version in terms of discoverability, but seemed cleaner/more compact and also kept the "identity" of the resultant object confined to being a Filter. You can also have helper functions scoped to just that object (although that also works for the class-based version).
tl; dr - It's primarily for code-navigation limitations in the IDE + a bit of laziness, but there are a couple of other considerations.