对于模糊的标题感到抱歉...不知道如何描述这一点。
我已经在Scala中看到/使用过某种代码构造已有一段时间但我不知道它是如何工作的。它看起来像这样(喷涂路由的例子):
path( "foo" / Segment / Segment ) { (a,b) => { // <-- What's this style with a,b?
...
}}
在此示例中,路径中的Segements分别绑定到关联块内的a和b。我知道如何使用这种模式,但它是如何工作的?为什么不把它绑定到“foo”?
我对喷雾如何为我的目的不感兴趣,但Scala的设施是什么,我将如何编写自己的?
答案 0 :(得分:11)
此代码来自扩展Directives
的类。所以Directives
的所有方法都在范围内。
/
中没有方法String
,因此使用an implicit conversion将String
转换为PathMatcher0
(PathMatcher[HNil]
)/
3}}
方法PathMatcher
需要PathMatcher
并返回PathMatcher1[String]
。
method /
是PathMatcher[String :: HNil]
(/
)。
具有PathMatcher[HNil]
参数的PathMatcher[String :: HNil]
的方法PathMatcher[String :: HNil]
会返回/
。
具有PathMatcher[String :: HNil]
参数的PathMatcher[String :: HNil]
的方法PathMatcher[String :: String :: HNil]
会返回PathMatcher[String :: String :: HNil]
。这是来自Segment
的黑魔法。请参阅异类列表shapeless
;值得一读。
因此,您使用Directive[String :: String :: HNil]
作为参数调用concatenation。它返回apply
。
然后,您使用Directive
(Function2[?, ?, ?]
)作为参数在(a, b) => ..
上调用方法Directive[A :: B :: C ...]
。对于使用方法apply((a: A, b: B, c: C ...) => Route)
创建对象的每个PathMatcher
,都有适当的隐式转换(请参阅黑魔法)。
HList
包含路径解析的规则。它将结果作为HNil
返回。
“foo”匹配器method path
一个字符串并忽略它(返回A / B
)。
A
匹配器将2个匹配器(B
和A
)组合在一起,用“/”字符串分隔。它使用B
连接来连接HList
和Segment
的结果。
String :: HNil
匹配器matches路径段并将其作为"foo" / Segment / Segment
返回。
因此String :: String :: HNil
匹配3个细分的路径,忽略第一个细分并将剩余的细分返回为Function2[String, String, Route]
。
然后黑魔法允许您使用(String, String) => Route
(String :: String :: HNil
)来处理{case a :: b :: HNil => ...}
。如果没有这样的魔力,你将不得不使用这样的方法:Directive[A :: B :: C ...]
。
正如@AlexIv所说:
对于使用方法apply((a: A, b: B, c: C ...) => Route)
创建对象的每个ApplyConverter
,都存在隐式转化matches。
隐含地接受ApplyConverter
。 (A, B, C ...) => Route
的类型成员pimpApply
代表每个Directive[A :: B :: C ...]
的适当函数ApplyConverter
。
如果没有宏或样板代码,则无法创建此类隐式值。因此In
用于生成{{1}}。请参阅sbt-boilerplate
。
答案 1 :(得分:5)
Senia的回答有助于理解Spray-routing指令以及他们如何使用HLists来完成他们的工作。但我得到的印象是你真的只对
中使用的Scala结构感兴趣path( "foo" / Segment / Segment ) { (a,b) => ... }
听起来好像您将此解释为特殊的Scala语法,以某种方式将这两个Segment
实例连接到a
和b
。事实并非如此。
path( "foo" / Segment / Segment )
只是对path
的普通调用,只有一个参数,一个涉及对/
方法的两次调用的表达式。没什么好看的,只是普通的方法调用。
该调用的结果是一个函数,它需要另一个函数 - 当匹配请求进入时你想要发生的事情 - 作为参数。这就是这部分:
{ (a,b) => ... }
这只是一个有两个参数的函数。第一部分(path
的调用)和第二部分(当收到匹配的消息时你想要做什么)不以任何方式语法连接。它们与Scala完全分开。但是, Spray的语义连接它们:第一部分创建一个函数,当收到匹配的消息时,它将调用第二部分。
答案 2 :(得分:1)
对 senia 回答的一些补充说明,这真的很好。
当你写这样的东西时:
path("foo" / Segment / Segment) { (a,b) => {...} }
您正在调用Directive上的应用方法,例如 senia ,但指令中没有apply
方法,因此使用隐式conversion来{ {3}}方法。正如您所能pimpApply
使用类型类型模式happly实现的那样,默认情况下仅为Directive0
定义。如您所见,它的伴随对象扩展ApplyConverterInstances
,它是使用ApplyConverter插件生成的
答案 3 :(得分:0)
至于我,它可以实现如下
方法path
采用任意类型参数,该类型的某个模式对象以及该类型的函数:
def path[T](pattern:Pattern[T])(function:Function[T, `some other type like unit or any`])
模式由两个技巧构成。
/
或隐式转换为Pattern[Nothing]
Pattern[T]
有方法/
,它构造了一些新类型的模式。该方法采用单个参数(Segment的一些祖先)。我猜 - 模式[T2]:
trait Pattern[T] {
///
def `/`[T2](otherPattern:Pattern[T2]):Pattern[(T,T2)]
}
path
的第一个参数允许将构造的模式类型确定为该对。因此我们得到第二个参数的正确类型。path
内完成。我认为这不属于问题范围。