Scala中的函数/方法。如何运作?

时间:2019-02-11 13:34:22

标签: scala function akka-http

我是Scala的新手,很难理解所有声明和使用函数的方式。有人可以逐步解释一下这里发生了什么吗?

我正在学习介绍Akka HTTP的课程。该代码有效,但我不理解route方法:

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

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

我们正在定义方法route,该方法被声明为path的值(从上一行导入),但是在path函数内部,我们有一个名为{{1 }}我不理解。

当我将get声明为一种方法时,我是覆盖它还是发生了什么?

如果有人可以逐行解释发生了什么,我会很乐意。而且不要介意这与Akka有关。我想了解Scala语法。

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

感谢所有出色的回答。我想我明白了!

所以总结一下我的版本。

path是一个需要字符串的函数。它返回另一个需要path()的函数。并且在Scala术语中,我们可以进行某种操作以直接将指令发送给返回的函数。

因此,块{}中的所有内容都发送到Directive返回的函数中。而且由于Scala中的一个块总是返回最后一行,所以我们返回的是path(),根据我们用get调用的相同原理。

complete也是一个函数,它带有一个参数并且可以写为一个块。这等效于只写get

再次感谢!

4 个答案:

答案 0 :(得分:28)

您不一定需要理解此答案中的所有内容即可有效地使用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 = ...

尽管这些getcomplete看起来像关键字之类的东西,但它们实际上只是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-reflect的reify作为非常有用的工具来帮助使这些内容可见。

从一个简单的(不相关的)示例开始,您可能想知道当看到诸如"a" * 3之类的Scala代码时发生了什么,特别是如果您知道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)))))

我们可以重新格式化化的表达式以提高可读性:

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")开始。从the API docs中我们可以看到Directives.path不是字符串,而是PathMatcher,因此我们知道从StringPathMatcher的隐式转换即将推出,在我们完全废弃的版本中,我们可以在这里看到它:

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

如果我们检查文档,当然可以肯定,_segmentStringToPathMatcher是适当类型的隐式转换。

complete("Hello, World!")中发生了类似的事情。 Directives.complete需要一个ToMarshallableResponse,而不是String,因此必须加入一个隐式转换。在这种情况下,它是ToResponseMarshallable.apply,它也需要一个隐式{{1} }实例,在这种情况下,它是通过从MarshallerToEntityMarshaller的隐式转换获得的,其中ToResponseMarshallable实例是ToEntityMarshaller,而转换器是{{ 1}}部分:

Marshaller.StringMarshaller

还记得上面我说过Marshaller.liftMarshaller只是 Directives.complete( ToResponseMarshallable.apply("Hello, World!")( Marshaller.liftMarshaller(Marshaller.StringMarshaller) ) ) 上的静态方法吗?从某种意义上说,这虽然是一个谎言,但它虽然是get上的静态方法,但在编写Directives时并未调用它。相反,此Directives实际上是一个返回get(...)的无参数方法。 getDirective0的类型别名,尽管Directive0没有Directive[Unit]方法,但可以通过{{ Directive[Unit]上的1}}方法。因此,当您编写apply时,Scala会将其还原为addByNameNullaryApply,然后将Directive的值转换为get(...)函数,该函数具有适当的get.apply(...)方法。 get部分也发生了完全相同的事情。

这种事情似乎是一场噩梦,作为Scala的长期用户,我可以告诉您,这肯定是经常发生的。不过,Route => Route和API文档之类的工具可以使它的可怕程度降低一些。

答案 1 :(得分:3)

在您的代码段中,Scala语言和编译器具有多个功能,让我们分析一下我所知道的功能:

def route = ...

定义一个不带参数的函数,其结果类型由其主体的返回值确定。

path("hello") {
  ...
}

我对path函数本身并不熟悉,但是似乎该片段中发生了三件事:

我不想花时间描述所有这些内容,因为互联网上充斥着大量的资源,可以对它们进行很好的解释。但是我想至少链接this great introductory article,这对我的早期工作有很大帮助。

链接的文章向您展示了一个完整的示例,说明了如何使用所有这三种功能来构建自己的控件结构,例如所使用的代码之一。

继续前进,一点点

get {
  ...  
}

还是以上几点的应用,但是这次没有麻烦,所以花括号是该函数的唯一参数。

complete("Hello, World!")

只是一个普通的旧函数调用。

简而言之,该代码使用一些“技巧”将函数调用转换为类似于特殊语言结构的样式,这可能会使初学者感到困惑。

该技术经常用于在Scala中编写特定于Domani的语言(DSL)。


答案 2 :(得分:2)

这里发生了很多事情,这是一个了解scala的非常复杂的示例。但是我会尽力的。

route的类型为Route,它是定义为type Route = RequestContext ⇒ Future[RouteResult]的类型别名,其中RequestContext ⇒ Future[RouteResult]是消耗RequestContext并产生{ {1}}。

Future[RouteResult]是一种创建path的方法。有一个隐式转换将Directive[Unit]转换为函数Directive[Unit](简化)。可以通过方法Route => Route或使用编译器糖apply(???)调用函数。

{???}是一种也创建get的方法,类似的方法也适用于它。

Directive[Unit]的类型为complete,扩展了StandardRoute

了解了所有这些信息后,我们可以丑化您的示例,该示例将写为

Route

答案 3 :(得分:2)

如果您看到这样的代码片段,可能会有所帮助:

导入akka.http.scaladsl.server.Directives。_

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

我添加了Route类型,向您展示了您只是在使用Akka HTTP提供的语法来构建路由,该语法允许您在更高级别上定义通用匹配条件,并将特定条件嵌套在该部分中。在这里,您正在使用Akka HTTP中的路由DSL功能。路由DSL将一些隐式引入范围。使用path方法可确保您能够处理针对路径host/hello进入主机的请求,这意味着您的主机现在可以处理针对路径/hello的获取请求。 path指令内部的代码体表示其他匹配条件,以检查我们是否有正确的路径匹配。完整的方法知道如何转换为HttpResponse。在这里,您将完成“ hello world”(纯文本)。

视情况而定,您可能还有其他HTTP方法标准请求,例如post,put,delete。甚至自定义HTTP方法。

这是用于处理Akka-HTTP中的HTTP请求的便捷DSL。检查Akka-HTTP文档here

相关问题