Функции / методы в Scala.Как это работает? - PullRequest
0 голосов
/ 11 февраля 2019

Я новичок в Scala и с трудом разбираюсь во всех способах объявления и использования функций.Может кто-нибудь объяснить, пожалуйста, шаг за шагом, что здесь происходит?

Я изучаю курс, который знакомит с Akka HTTP.Код работает, но я не понимаю метод маршрута:

import akka.http.scaladsl.server.Directives._

def route = path("hello") {
    get {
      complete("Hello, World!")
    }
  }

Мы определяем метод route, который объявляет значение path (импортированное из строки выше), но затемвнутри функции path есть нечто под названием get, которое я не понимаю.

И когда я объявляю path как метод, я переопределяю это или что происходит?

Мне бы очень хотелось, если бы кто-то мог объяснить, что происходит, строка за строкой.И не против, что это Акка.Я хочу знать о синтаксисе Scala.

================================================================

Спасибо за отличные ответы.Я думаю, я понял!

Итак, подведем итог моей версии.

path() - это функция, которая хочет строку.Он возвращает другую функцию, которая хочет Directive.И в Scala lingo мы можем сделать какое-то карри для прямой отправки директивы возвращаемой функции.

Таким образом, все в блоке {} отправляется функции, которую возвращает path().А поскольку блок в Scala всегда возвращает последнюю строку, мы возвращаем get, который по тем же принципам мы называем complete.

get также является функцией, которая принимает один параметр и может быть записана как блок.Это равносильно написанию get(complete("Hello, world")).

Еще раз спасибо!

Ответы [ 4 ]

0 голосов
/ 11 февраля 2019

Вам не обязательно понимать все в этом ответе, чтобы эффективно использовать akka-http, но я гарантирую, что будут времена - вероятно, скорее, чем позже - что вы будете бороться с компилятором и просто захотите всегоот причудливого синтаксического сахара, чтобы уйти, и хорошая новость в том, что есть инструменты, которые делают это возможным (плохая новость в том, что, как только вы избавитесь от причудливого синтаксиса, реальность может стать ужасным беспорядком).

Первое, на что нужно обратить внимание, - это то, что фигурные скобки здесь могут выглядеть очень похоже на разделители области действия или определения из Java или других языков, но на самом деле они просто применяют методы к аргументам.Вы можете сделать то же самое с помощью круглых скобок:

scala> import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Directives._

scala> val route = path("hello")(get(complete("Hello, World!")))
route: akka.http.scaladsl.server.Route = ...

И хотя эти get и complete вещи могут выглядеть как ключевые слова или что-то подобное, на самом деле это просто статические методы на Directives (примерно -Прочитайте всю эту вещь для полной истории), поэтому следующее также эквивалентно:

scala> import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.server.Directives

scala> val route = Directives.path("hello")(
     |   Directives.get(Directives.complete("Hello, World!"))
     | )
route: akka.http.scaladsl.server.Route = ...

Это, надеюсь, объясняет некоторые синтаксисы, но здесь все еще происходит много невидимого.Если вы находитесь в REPL, вы можете использовать scala-refle reify как чрезвычайно полезный инструмент, чтобы помочь сделать этот материал видимым.

Для начала с простого (несвязанного) примера, вы можете задаться вопросом, что происходиткогда вы видите Scala-код, такой как "a" * 3, особенно если вы знаете, что строки Java не имеют оператора *, поэтому вы открываете REPL:

scala> import scala.reflect.runtime.universe.reify
import scala.reflect.runtime.universe.reify

scala> reify("a" * 3).tree
res6: reflect.runtime.universe.Tree = Predef.augmentString("a").$times(3)

И есть отлаженная версия, показывающаянеявный метод, который применяется к строке, чтобы дать ей оператор *.

В вашем случае вы могли бы написать что-то вроде этого:

scala> import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Directives._

scala> import scala.reflect.runtime.universe.reify
import scala.reflect.runtime.universe.reify

scala> reify(path("hello")(get(complete("Hello, World!")))).tree
res0: reflect.runtime.universe.Tree = Directive.addByNameNullaryApply(Directives.path(Directives._segmentStringToPathMatcher("hello"))).apply(Directive.addByNameNullaryApply(Directives.get).apply(Directives.complete(ToResponseMarshallable.apply("Hello, World!")(Marshaller.liftMarshaller(Marshaller.StringMarshaller)))))

Мы можем переформатировать выражение reified для удобства чтения:

Directive.addByNameNullaryApply(
  Directives.path(
    Directives._segmentStringToPathMatcher("hello")
  )
).apply(
  Directive.addByNameNullaryApply(Directives.get).apply(
    Directives.complete(
      ToResponseMarshallable.apply("Hello, World!")(
        Marshaller.liftMarshaller(Marshaller.StringMarshaller)
      )
    )
  )
)

Если вы добавите пару импортов, это также вполне допустимый код Scala:

scala> import akka.http.scaladsl.server.{ Directive, Directives }
import akka.http.scaladsl.server.{Directive, Directives}

scala> import akka.http.scaladsl.marshalling.{ Marshaller, ToResponseMarshaller }
import akka.http.scaladsl.marshalling.{Marshaller, ToResponseMarshaller}

scala> val route = Directive.addByNameNullaryApply(
     |   Directives.path(
     |     Directives._segmentStringToPathMatcher("hello")
     |   )
     | ).apply(
     |   Directive.addByNameNullaryApply(Directives.get).apply(
     |     Directives.complete(
     |       ToResponseMarshallable.apply("Hello, World!")(
     |         Marshaller.liftMarshaller(Marshaller.StringMarshaller)
     |       )
     |     )
     |   )
     | )
route: akka.http.scaladsl.server.Route = ...

Чтобы объяснить это шаг за шагом, мы можем начать с path("hello").Из API-документов видно, что Directives.path принимает не строку, а PathMatcher, поэтому мы знаем, что начинается неявное преобразование из String в PathMatcher,и в нашей полностью отлаженной версии мы можем видеть это здесь:

  Directives.path(
    Directives._segmentStringToPathMatcher("hello")
  )

И, конечно же, если мы проверим документы, _segmentStringToPathMatcher - неявное преобразование соответствующего типа.

Aподобное происходит в complete("Hello, World!").Directives.complete принимает ToMarshallableResponse, а не String, поэтому должно быть задействовано неявное преобразование. В этом случае это ToResponseMarshallable.apply, что также требует неявного экземпляра Marshaller, который в этом случае он получает черезнеявное преобразование из ToEntityMarshaller в ToResponseMarshallable, где экземпляр ToEntityMarshaller равен Marshaller.StringMarshaller, а преобразователь является частью Marshaller.liftMarshaller:

    Directives.complete(
      ToResponseMarshallable.apply("Hello, World!")(
        Marshaller.liftMarshaller(Marshaller.StringMarshaller)
      )
    )

Помните, как я сказал выше get был просто статический метод на Directives?Это было своего рода ложью, в том смысле, что, хотя это статический метод в Directives, мы не вызываем его, когда пишем get(...).Вместо этого get на самом деле является методом без аргументов, который возвращает Directive0.Directive0 является псевдонимом типа для Directive[Unit], и хотя Directive[Unit] не имеет метода apply, он может быть неявно преобразован в вещь, которая делает это, с помощью метода addByNameNullaryApply в Directive.Поэтому, когда вы пишете get(...), Scala обрабатывает это значение как get.apply(...), а затем преобразует значение get в функцию Route => Route, которая имеет соответствующий метод apply.И точно так же происходит с партией path("hello")(...).

Подобные вещи могут показаться кошмаром, и как давний пользователь Scala я могу вам сказать, что это определенно часто происходит.Такие инструменты, как reify и API-документы, могут сделать его немного менее ужасным.

0 голосов
/ 11 февраля 2019

Это может помочь, если вы увидите фрагмент кода следующим образом:

import akka.http.scaladsl.server.Directives ._

def route: Route = path("hello") {
    get {
      complete("Hello, World!")
    }
  }

Я добавил тип Route впокажем вам, что вы просто строите маршрут, используя синтаксис, предоставленный Akka HTTP, который позволяет вам определять общие критерии соответствия на более высоком уровне и вкладывать конкретные критерии в этот раздел.Здесь вы используете функциональность DSL маршрутизации в Akka HTTP.DSL маршрутизации приносит некоторые последствия в область.Использование метода path гарантирует, что вы сможете обрабатывать запросы, поступающие на хост для пути host/hello, что означает, что ваш хост теперь может обрабатывать запрос на получение запроса для пути /hello.Тело кода внутри директивы path представляет дополнительные критерии соответствия, чтобы проверить, когда у нас есть правильное соответствие пути.Полный метод знает, как преобразовать в HttpResponse.Здесь вы заканчиваете "привет мир", простой текст.

У вас могут быть дополнительные запросы HTTP, стандартные запросы, такие как post, put, delete, в зависимости от обстоятельств.Или даже пользовательские методы HTTP.

Это удобный DSL для обработки HTTP-запросов в Akka-HTTP.Проверьте документ Akka-HTTP здесь

0 голосов
/ 11 февраля 2019

В ваших фрагментах есть несколько особенностей языка Scala и компилятора, давайте проанализируем те, которые мне известны:

def route = ...

определяет функции без аргументов и с типом результата, определяемым возвращаемым значениемего тело.

path("hello") {
  ...
}

Я не знаком с самой функцией path, но кажется, что в этом фрагменте происходит три вещи:

Я не хочу тратить время на описание всех из них, поскольку интернет переполненресурсов, которые объясняют их в значительной степени.Но я хочу связать по крайней мере эту замечательную вводную статью , которая очень помогла мне в мои первые годы.

Связанная статья показывает вам полный пример того, как использовать все три функции для построенияваша собственная структура управления, такая как код, который вы используете.

Если двигаться дальше, бит

get {
  ...  
}

снова является применением вышеуказанных пунктов, но на этот раз нетcurry, поэтому фигурные скобки являются единственным аргументом функции.

complete("Hello, World!")

Это просто старый вызов функции.

Короче этот код использует некоторые "трюки"«которые превращают вызов функции в нечто, напоминающее специальную языковую конструкцию, и это может создать путаницу для начинающих.

Этот метод часто используется для написания Domani-Specific Languages ​​(DSL) в Scala.


0 голосов
/ 11 февраля 2019

Здесь происходит много вещей, и это довольно сложный пример для понимания scala.Но я попытаюсь.

Тип route - Route, который является псевдонимом типа, определенным как type Route = RequestContext ⇒ Future[RouteResult], где RequestContext ⇒ Future[RouteResult] - это функция, которая потребляет RequestContext и производит Future[RouteResult].

path - это метод, который создает Directive[Unit].Существует неявное преобразование, которое преобразует Directive[Unit] в функцию Route => Route (упрощенно).Функция может быть вызвана методом apply или с помощью сахара-компилятора как (???) или {???}.

get - это метод, который также создает Directive[Unit], и к нему применяется аналогичный подход.

complete имеет тип StandardRoute, который расширяет Route.

Зная все это, мы могли бы уточнить ваш пример, который будет записан как

path("hello").apply { ctx =>
    val inner: Route = { ctx =>
      ctx.complete("done")
    }
    get.apply(inner).apply(ctx)
}
...