Scala parameter pattern (spray routing example)

Sorry for the vague name ... was not sure how to characterize this.

I have seen / used some code construction in Scala for some time, but I don't know how this works. It looks like this (example from spray routing):

path( "foo" / Segment / Segment ) { (a,b) => { // <-- What this style with a,b? ... }} 

In this example, the Segements in the path are bound to a and b, respectively, inside the linked block. I know how to use this template, but how does it work? Why didn't he bind something to "foo"?

I'm not so interested in how the spray works for my purpose here, but what is this Scala object, and how can I write my own?

+7
source share
4 answers

Senina's answer is useful in understanding Spray-routing directives and how they use HLists to do their job. But it seems that you were really interested in the Scala constructs used in

 path( "foo" / Segment / Segment ) { (a,b) => ... } 

It sounds like you are interpreting this as a special Scala syntax that somehow connects the two instances of Segment with a and b . It's not like that at all.

 path( "foo" / Segment / Segment ) 

is just a normal call to path with one argument, an expression that includes two calls to / . Nothing unusual, just a regular method call.

The result of this call is a function that wants to use another function - the thing you want to execute when the corresponding request arrives - as an argument. What is this part:

 { (a,b) => ... } 

This is just a function with two arguments. The first part (the path call) and the second part (what you want to do when the corresponding message is received) are not syntactically related. They are completely divided into Scala. However, the Spray semantics connect them: the first part creates a function that will call the second part when it receives the corresponding message.

+5
source

This code belongs to a class that extends Directives . Thus, all Directives methods are in scope.

Pathmatcher

There is no / method in String , so an implicit conversion is used to convert String to PathMatcher0 ( PathMatcher[HNil] ) with method / .

The / method takes a PathMatcher and returns a PathMatcher .

Segment is PathMatcher1[String] ( PathMatcher[String :: HNil] ).

The PathMatcher[HNil] method / parameter with the PathMatcher[String :: HNil] returns a PathMatcher[String :: HNil] .

The method / from PathMatcher[String :: HNil] with the parameter PathMatcher[String :: HNil] returns a PathMatcher[String :: String :: HNil] . This is black magic from shapeless . See Heterogeneous Concatenation Lists; it's worth a read.

Directive

So, you call the path method with PathMatcher[String :: String :: HNil] as a parameter. It returns Directive[String :: String :: HNil] .

Then you call the apply method on Directive with Function2[?, ?, ?] ( (a, b) => .. ) as the parameter. There is a corresponding implicit conversion (see Black Magic) for each Directive[A :: B :: C ...] that creates an object using the apply((a: A, b: B, c: C ...) => Route) method apply((a: A, b: B, c: C ...) => Route) .

Syntactic

PathMatcher contains rules for parsing a path. It returns its result as an HList .

The "foo" matcher matches the string and ignores it (returns HNil ).

A / B connector combines 2 sets ( A and B ), separated by the string "/". It combines the results of A and B using HList concatenation.

Segment segment matcher matches the segment of the path and returns it as String :: HNil .

So, "foo" / Segment / Segment matches a path of three segments, ignores the first 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 this method: {case a :: b :: HNil => ...} .

Black magic

As @AlexIv noted:

There is an implicit pimpApply conversion for each Directive[A :: B :: C ...] that creates an object with the apply((a: A, b: B, c: C ...) => Route) method apply((a: A, b: B, c: C ...) => Route) .

It accepts ApplyConverter implicitly. An element of type In ApplyConverter is a suitable function (A, B, C ...) => Route for each Directive[A :: B :: C ...] .

It is not possible to create such implicit values ​​without macros or templates. Therefore, sbt-boilerplate used to generate ApplyConverter . See ApplyConverterInstances.scala .

+11
source

Some additional note for senia's answer, which is really good.

When you write something like this:

 path("foo" / Segment / Segment) { (a,b) => {...} } 

you call the apply method on Directive , for example senia , but there is no apply method in the directive, so the spray uses an implicit conversion to the happly method. As you can, pimpApply is implemented using the typeclass ApplyConverter template, which by default is defined only for Directive0 . As you can see, its companion object extends the ApplyConverterInstances , which is generated using the sbt-bolierplate plugin

+1
source
  • Why don't you look at the source?
  • As for me, it can be implemented as follows

    • The path method accepts an arbitrary type parameter, some template object of this type, and a function from this type:

       def path[T](pattern:Pattern[T])(function:Function[T, `some other type like unit or any`]) 
    • The template is built with two tricks.

      • The string is either "wrapped" to have the / method, or has an implicit conversion to Pattern[Nothing]
      • Pattern[T] has a / method that creates another pattern with some new type. The method takes one argument (some segment ancestor). I think - Pattern [T2]:

         trait Pattern[T] { /// def `/`[T2](otherPattern:Pattern[T2]):Pattern[(T,T2)] } 
  • So, the first argument to path allows you to define the constructed template type as a pair. Thus, we get the correct type for the second argument.
  • Actual reconciliation work is done inside path . I thought this was beyond the scope of the questions.
0
source

Source: https://habr.com/ru/post/952333/


All Articles