Sorry about the vague title...wasn't sure how to characterize this.
I've seen/used a certain code construction in Scala for some time but I don't know how it works. It looks like this (example from Spray routing):
path( "foo" / Segment / Segment ) { (a,b) => { // <-- What's this style with a,b?
...
}}
In this example, the Segements in the path are bound to a and b respectively inside the associated block. I know how to use this pattern but how does it work? Why didn't it bind something to "foo"?
I'm not so interested in how spray works for my purpose here, but what facility of Scala is this, and how would I write my own?
Some additional note to senia answer, which is really good.
When you write something like this:
you are calling apply method on Directive like senia wrote, but there is no
apply
method in Directive, so spray is using implicit conversion to happly method. As you canpimpApply
is implemented with typeclass pattern ApplyConverter, which is defined only forDirective0
by default. As you can see it's companion object extendsApplyConverterInstances
, which is generated with sbt-bolierplate pluginAs for me, it could be implemented as follows
method
path
takes arbitrary type parameter, some pattern-object of that type and a function from that type:the pattern is constructed with two tricks.
/
or has an implicit convertion toPattern[Nothing]
the
Pattern[T]
has method/
that constructs another pattern with some new type. The method takes single argument (some ancestor of Segment). I guess — Pattern[T2]:path
allows to determine the constructed type of pattern as being the pair. Thus we get proper type for the second argument.path
. I thought it was out of the questions scope.Senia's answer is helpful in understanding the Spray-routing directives and how they use HLists to do their work. But I get the impression you were really just interested in the Scala constructs used in
It sounds as though you are interpreting this as special Scala syntax that in some way connects those two
Segment
instances toa
andb
. That is not the case at all.is just an ordinary call to
path
with a single argument, an expression involving two calls to a/
method. Nothing fancy, just an ordinary method invocation.The result of that call is a function which wants another function -- the thing you want to happen when a matching request comes in -- as an argument. That's what this part is:
It's just a function with two arguments. The first part (the invocation of
path
) and the second part (what you want done when a matching message is received) are not syntactically connected in any way. They are completely separate to Scala. However, Spray's semantics connects them: the first part creates a function that will call the second part when a matching message is received.This code is from a class that extends
Directives
. So all methods ofDirectives
are in scope.PathMatcher
There is no method
/
inString
, so an implicit conversion is used to convertString
toPathMatcher0
(PathMatcher[HNil]
) with method/
.Method
/
takes aPathMatcher
and returns aPathMatcher
.Segment
is aPathMatcher1[String]
(PathMatcher[String :: HNil]
).Method
/
ofPathMatcher[HNil]
withPathMatcher[String :: HNil]
parameter returns aPathMatcher[String :: HNil]
.Method
/
ofPathMatcher[String :: HNil]
withPathMatcher[String :: HNil]
parameter returns aPathMatcher[String :: String :: HNil]
. It's black magic fromshapeless
. See heterogenous lists concatenation; it is worth reading.Directive
So you are calling method
path
withPathMatcher[String :: String :: HNil]
as a parameter. It returns aDirective[String :: String :: HNil]
.Then you are calling method
apply
onDirective
withFunction2[?, ?, ?]
((a, b) => ..
) as a parameter. There is an appropriate implicit conversion (see black magic) for everyDirective[A :: B :: C ...]
that creates an object with methodapply((a: A, b: B, c: C ...) => Route)
.Parsing
PathMatcher
contains rules for path parsing. It returns its result as anHList
.The "foo" matcher matches a String and ignores it (returns
HNil
).The
A / B
matcher combines 2 matchers (A
andB
) separated by a "/" string. It concatenates the results ofA
andB
usingHList
concatenation.The
Segment
matcher matches a path segment and returns it as aString :: HNil
.So
"foo" / Segment / Segment
matches a path of 3 segments, ignores the first one and returns the remaining segments asString :: String :: HNil
.Then black magic allows you to use
Function2[String, String, Route]
((String, String) => Route
) to processString :: String :: HNil
. Without such magic you would have to use the method like this:{case a :: b :: HNil => ...}
.Black magic
As @AlexIv noted:
There is an implicit conversion
pimpApply
for everyDirective[A :: B :: C ...]
that creates an object with methodapply((a: A, b: B, c: C ...) => Route)
.It accepts
ApplyConverter
implicitly. Type memberIn
ofApplyConverter
represents an appropriate function(A, B, C ...) => Route
for everyDirective[A :: B :: C ...]
.There is no way to create such implicit values without macros or boilerplate code. So
sbt-boilerplate
is used forApplyConverter
generation. SeeApplyConverterInstances.scala
.