问题
I'd like to understand how directives in Spray work. As per the documentation:
The general anatomy of a directive is as follows:
name(arguments) { extractions => ... // inner Route }
My basic understanding is that in the below snippet, 32
is passed as a parameter to method test
.
test {
32
}
However, in the above directive name
example, it is said arguments are passed into inner route, which is an anonymous function.
Could someone please help me understand the syntax and the flow starting from how the arguments are extracted and passed into an inner route?
回答1:
You're right that that syntax passes 32
to the function test
. What you're missing is that a Directive
accepts a function as an argument (remember, we're doing functional programming now so functions are values). If you wanted to write this:
path(IntNumber) {
userId =>
complete(s"Hello user $userId")
}
in a less DSL-ey fashion, you could do this:
val innerFunction: Int => Route = {userId => complete(s"Hello user $userId")}
(path(IntNumber))(innerFunction)
or even this:
def innerMethod(userId: Int): Route = complete(s"Hello user $userId")
(path(IntNumber))(innerMethod)
The mechanics of how this is actually accomplished are... complex; this method makes a Directive
implicitly convertible to a function:
implicit def pimpApply[L <: HList](directive: Directive[L])(implicit hac: ApplyConverter[L]): hac.In ⇒ Route = f ⇒ directive.happly(hac(f))
This is using the "magnet pattern" to select an appropriate hac
, so that it can take a function in the inner path (with an appropriate number of arguments) if the directive extracts parameters, or a value in the inner path (a plain route) if the directive doesn't extract parameters. The code looks more complicated than it is because scala doesn't have direct support for full dependent typing, so we have to emulate it via implicits. See ApplyConverterInstances
for the horrible code this necessitates :/.
The actual extracting all happens when we get an actual route, in the happly
method of the specific directive. (If everything used HList
everywhere, we could mostly avoid/ignore the preceding horrors). Most extract-ey directives (e.g. path
) eventually call hextract
:
def hextract[L <: HList](f: RequestContext ⇒ L): Directive[L] = new Directive[L] {
def happly(inner: L ⇒ Route) = ctx ⇒ inner(f(ctx))(ctx)
}
Remember a Route
is really just a RequestContext => Unit
, so this returns a Route
that, when passed a RequestContext
:
- Runs
f
on it, to extract the things that need extracting (e.g. URL path components) - Runs
inner
on that;inner
is a function from e.g. path components to the inner route. - Runs that inner route, on the context.
(The following was edited in by a mod from a comment conversation):
Fundamentally it's pretty elegant, and it's great that you can see all the spray code and it's ordinary scala code (I really recommend reading the source when you're confused). But the "bridging" part with the ApplyConverter
is complex, and there's really no way around that; it comes of trying to do full dependent types in a language that wasn't really designed for them.
You've got to remember that the spray routing DSL is a DSL; it's the kind of thing that you'd have to have as an external config file in almost any other language. I can't think of a single web framework that offers the same flexibility in routing definitions that spray does with complete compile-time type safety. So yes, some of the things spray does are complex - but as the quote goes, easy things should be easy and hard things should be possible. All the scala-level things are simple; spray is complex, but it would be even more complex (unusably so) in another language.
来源:https://stackoverflow.com/questions/27777668/how-do-directives-work-in-spray