7

抱歉标题含糊……不知道如何描述这一点。

一段时间以来,我已经在 Scala 中看到/使用了某种代码结构,但我不知道它是如何工作的。它看起来像这样(来自喷雾路由的示例):

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

在此示例中,路径中的分段分别绑定到关联块内的 a 和 b。我知道如何使用这种模式,但它是如何工作的?为什么它没有将某些东西绑定到“foo”?

我对喷雾如何在这里达到我的目的不太感兴趣,但是这是 Scala 的什么工具,我将如何编写自己的工具?

4

4 回答 4

11

此代码来自扩展的类Directives。所以所有的方法Directives都在范围内。

路径匹配器

中没有方法/String所以使用隐式转换来转换StringPathMatcher0( PathMatcher[HNil]) with method/

方法/接受 aPathMatcher并返回 a PathMatcher

Segment是一个PathMatcher1[String]( PathMatcher[String :: HNil])。

带参数/的方法返回一个.PathMatcher[HNil]PathMatcher[String :: HNil]PathMatcher[String :: HNil]

带参数/的方法返回一个. 这是来自的黑魔法。见异构列表连接;值得一读。PathMatcher[String :: HNil]PathMatcher[String :: HNil]PathMatcher[String :: String :: HNil]shapeless

指示

所以你正在调用方法pathPathMatcher[String :: String :: HNil]作为参数。它返回一个Directive[String :: String :: HNil].

然后你用( ) 作为参数调用方法apply。每个使用方法创建对象的方法都有一个适当的隐式转换(参见黑魔法) 。DirectiveFunction2[?, ?, ?](a, b) => ..Directive[A :: B :: C ...]apply((a: A, b: B, c: C ...) => Route)

解析

PathMatcher包含路径解析规则。它将结果作为HList.

“foo”匹配器匹配一个字符串并忽略它(返回HNil)。

A / B匹配器组合了 2 个匹配器 (和A) B,由 "/" 字符串分隔。它连接AB使用HList连接的结果。

Segment匹配器匹配路径段并将其作为String :: HNil.

因此"foo" / Segment / Segment匹配 3 个段的路径,忽略第一个段并将剩余段返回为String :: String :: HNil.

然后黑魔法允许你使用Function2[String, String, Route]( (String, String) => Route) 来处理String :: String :: HNil。如果没有这样的魔法,你将不得不使用这样的方法:{case a :: b :: HNil => ...}.

黑魔法

正如@AlexIv 所说:

pimpApply每个Directive[A :: B :: C ...]使用方法创建对象的隐式转换apply((a: A, b: B, c: C ...) => Route)

ApplyConverter隐式接受。的类型In成员ApplyConverter代表(A, B, C ...) => Route每个Directive[A :: B :: C ...].

如果没有宏或样板代码,就无法创建此类隐式值。所以sbt-boilerplate用于ApplyConverter生成。见ApplyConverterInstances.scala

于 2013-08-23T06:43:58.497 回答
5

Senia 的回答有助于理解 Spray-routing 指令以及它们如何使用 HList 来完成工作。但我觉得你真的只是对 Scala 中使用的结构感兴趣

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

听起来好像您将其解释为特殊的 Scala 语法,以某种方式将这两个Segment实例连接到ab。根本不是这样。

path( "foo" / Segment / Segment )

只是一个path带有单个参数的普通调用,一个涉及对一个/方法的两次调用的表达式。没什么花哨的,只是一个普通的方法调用。

该调用的结果是一个需要另一个函数的函数——当匹配请求进来时你想要发生的事情——作为参数。这就是这部分的内容:

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

它只是一个带有两个参数的函数。第一部分(对 的调用path)和第二部分(接收到匹配消息时您想要做的事情)在语法上没有任何联系。它们与 Scala 完全分离。然而,Spray 的语义将它们联系起来:第一部分创建一个函数,该函数将在收到匹配消息时调用第二部分。

于 2014-12-13T05:38:38.710 回答
1

对senia答案的一些附加说明,这真的很好。

当你写这样的东西时:

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

您正在像senia写的那样在Directive上调用 apply 方法,但是 Directive 中没有方法,因此 Spray 正在使用隐式转换happly方法。您可以使用类型类模式ApplyConverter实现,该模式仅在默认情况下定义。如您所见,它的伴生对象 extends ,它是使用sbt-bolierplate插件生成的applypimpApplyDirective0ApplyConverterInstances

于 2013-08-23T07:27:10.127 回答
0
  1. Why don't you look into source?
  2. As 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:

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

      • The String is either "pimped" to have method / or has an implicit convertion to Pattern[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]:

        trait Pattern[T] {
            ///
            def `/`[T2](otherPattern:Pattern[T2]):Pattern[(T,T2)] 
        }
        
  3. So the first argument of path allows to determine the constructed type of pattern as being the pair. Thus we get proper type for the second argument.
  4. The actual matching work is done inside path. I thought it was out of the questions scope.
于 2013-08-23T06:17:52.327 回答