Как работают директивы в 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:

  1. Запускается f на нем, чтобы извлечь то, что нужно извлечь (например, компоненты пути URL)
  2. Запускается inner на этом; inner является функцией, например, от компонентов пути до внутреннего маршрута.
  3. Запускает этот внутренний маршрут в контексте.

(Следующее было отредактировано модом из комментария):

По сути, это довольно элегантно, и это здорово, что вы можете видеть весь код спрея и его обычный скала-код (я действительно рекомендую читать исходный код, когда вы запутались). Но "соединяющая" часть с ApplyConverter является сложным, и нет никакого способа обойти это; речь идет о попытке сделать полностью зависимые типы в языке, который на самом деле не предназначен для них.

Вы должны помнить, что DSL маршрутизации распыления является DSL; это то, что вам нужно иметь в качестве внешнего конфигурационного файла практически на любом другом языке. Я не могу представить себе единую веб-инфраструктуру, которая предлагает такую ​​же гибкость в определениях маршрутизации, как и спрей, с полной безопасностью типов во время компиляции. Так что да, некоторые вещи, которые спрей делает, сложны - но, как говорится, простые вещи должны быть легкими, а сложные - возможными. Все вещи уровня скалы просты; спрей сложен, но на другом языке это будет еще сложнее (необычно).

Другие вопросы по тегам