Как работают директивы в Spray?
Я хотел бы понять, как работают директивы в Spray. Согласно документации:
Общая анатомия директивы следующая:
name(arguments) { extractions => ... // inner Route }
Я понимаю, что в приведенном ниже фрагменте, 32
передается в качестве параметра в метод test
,
test {
32
}
Однако в вышеуказанной директиве name
Например, сказано, что аргументы передаются во внутренний маршрут, который является анонимной функцией.
Может ли кто-нибудь помочь мне понять синтаксис и последовательность, начиная с того, как аргументы извлекаются и передаются во внутренний маршрут?
1 ответ
Вы правы, что этот синтаксис проходит 32
к функции test
, Что вы упускаете, так это то, что Directive
принимает функцию в качестве аргумента (помните, мы сейчас занимаемся функциональным программированием, поэтому функции являются значениями). Если вы хотите написать это:
path(IntNumber) {
userId =>
complete(s"Hello user $userId")
}
в менее DSL-эй стиле вы могли бы сделать это:
val innerFunction: Int => Route = {userId => complete(s"Hello user $userId")}
(path(IntNumber))(innerFunction)
или даже это:
def innerMethod(userId: Int): Route = complete(s"Hello user $userId")
(path(IntNumber))(innerMethod)
Механика того, как это на самом деле достигается... сложна; этот метод делает Directive
неявно преобразуется в функцию:
implicit def pimpApply[L <: HList](directive: Directive[L])(implicit hac: ApplyConverter[L]): hac.In ⇒ Route = f ⇒ directive.happly(hac(f))
Это использует "рисунок магнита", чтобы выбрать подходящий hac
, так что он может принимать функцию во внутреннем пути (с соответствующим количеством аргументов), если директива извлекает параметры, или значение во внутреннем пути (простой маршрут), если директива не извлекает параметры. Код выглядит более сложным, чем он есть, потому что у scala нет прямой поддержки полностью зависимой типизации, поэтому мы должны эмулировать его через имплициты. Увидеть ApplyConverterInstances
для ужасного кода это требует:/.
Фактическое извлечение всего происходит, когда мы получаем фактический маршрут, в happly
метод конкретной директивы. (Если бы все использовали HList
везде мы в основном могли избежать / игнорировать предшествующие ужасы). Большинство директив извлечения (например, path
) в конце концов позвоните hextract
:
def hextract[L <: HList](f: RequestContext ⇒ L): Directive[L] = new Directive[L] {
def happly(inner: L ⇒ Route) = ctx ⇒ inner(f(ctx))(ctx)
}
Помните Route
на самом деле просто RequestContext => Unit
так что это возвращает Route
что, когда прошло RequestContext
:
- Запускается
f
на нем, чтобы извлечь то, что нужно извлечь (например, компоненты пути URL) - Запускается
inner
на этом;inner
является функцией, например, от компонентов пути до внутреннего маршрута. - Запускает этот внутренний маршрут в контексте.
(Следующее было отредактировано модом из комментария):
По сути, это довольно элегантно, и это здорово, что вы можете видеть весь код спрея и его обычный скала-код (я действительно рекомендую читать исходный код, когда вы запутались). Но "соединяющая" часть с ApplyConverter
является сложным, и нет никакого способа обойти это; речь идет о попытке сделать полностью зависимые типы в языке, который на самом деле не предназначен для них.
Вы должны помнить, что DSL маршрутизации распыления является DSL; это то, что вам нужно иметь в качестве внешнего конфигурационного файла практически на любом другом языке. Я не могу представить себе единую веб-инфраструктуру, которая предлагает такую же гибкость в определениях маршрутизации, как и спрей, с полной безопасностью типов во время компиляции. Так что да, некоторые вещи, которые спрей делает, сложны - но, как говорится, простые вещи должны быть легкими, а сложные - возможными. Все вещи уровня скалы просты; спрей сложен, но на другом языке это будет еще сложнее (необычно).