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?
This code is from a class that extends Directives
. So all methods of Directives
are in scope.
There is no method /
in String
, so an implicit conversion is used to convert String
to PathMatcher0
(PathMatcher[HNil]
) with method /
.
Method /
takes a PathMatcher
and returns a PathMatcher
.
Segment
is a PathMatcher1[String]
(PathMatcher[String :: HNil]
).
Method /
of PathMatcher[HNil]
with PathMatcher[String :: HNil]
parameter returns a PathMatcher[String :: HNil]
.
Method /
of PathMatcher[String :: HNil]
with PathMatcher[String :: HNil]
parameter returns a PathMatcher[String :: String :: HNil]
. It's black magic from shapeless
. See heterogenous lists concatenation; it is worth reading.
So you are calling method path
with PathMatcher[String :: String :: HNil]
as a parameter. It returns a Directive[String :: String :: HNil]
.
Then you are calling method apply
on Directive
with Function2[?, ?, ?]
((a, b) => ..
) as a parameter. There is an appropriate implicit conversion (see black magic) for every Directive[A :: B :: C ...]
that creates an object with method apply((a: A, b: B, c: C ...) => Route)
.
PathMatcher
contains rules for path parsing. It returns its result as an HList
.
The "foo" matcher matches a String and ignores it (returns HNil
).
The A / B
matcher combines 2 matchers (A
and B
) separated by a "/" string. It concatenates the results of A
and B
using HList
concatenation.
The Segment
matcher matches a path segment and returns it as a String :: HNil
.
So "foo" / Segment / Segment
matches a path of 3 segments, ignores the first one and returns the remaining segments as String :: String :: HNil
.
Then black magic allows you to use Function2[String, String, Route]
((String, String) => Route
) to process String :: String :: HNil
. Without such magic you would have to use the method like this: {case a :: b :: HNil => ...}
.
As @AlexIv noted:
There is an implicit conversion pimpApply
for every Directive[A :: B :: C ...]
that creates an object with method apply((a: A, b: B, c: C ...) => Route)
.
It accepts ApplyConverter
implicitly. Type member In
of ApplyConverter
represents an appropriate function (A, B, C ...) => Route
for every Directive[A :: B :: C ...]
.
There is no way to create such implicit values without macros or boilerplate code. So sbt-boilerplate
is used for ApplyConverter
generation. See ApplyConverterInstances.scala
.