Scala 中的函数/方法.这是如何运作的? [英] Functions / methods in Scala. How does this work?

查看:30
本文介绍了Scala 中的函数/方法.这是如何运作的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am new to Scala and have a hard time understanding all the ways of declaring and using functions. Can someone please explain, step by step, what is going on here?

我正在学习介绍 Akka HTTP 的课程.代码有效,但我不明白路由方法:

I am following a course that introduces Akka HTTP. The code works, but I do not understand the route method:

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

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

我们正在定义一个方法 route,它被声明为 path 的值(从上面的行中导入),然后在 path 内部> 函数我们有一个叫做 get 的东西,我不明白.

We are defining a method route that is being declared the value of path (imported from the line above), but then inside the path function we have something called get that I don't understand.

当我将 path 声明为一个方法时,我是否覆盖了它,或者发生了什么?

And when I am declaring path as a method, am I overriding it, or what's going on?

如果有人能逐行解释正在发生的事情,我会很高兴.并且不要介意它涉及 Akka.我想了解 Scala 语法.

I would love if someone can explain what is going on, line by line. And don't mind that it is Akka involved. I want to know about the Scala syntax.

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

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

感谢所有精彩的回答.我想我明白了!

Thanks for all the great answers. I think I get it!

总结一下我的版本.

path() 是一个需要字符串的函数.它返回另一个需要 Directive 的函数.在 Scala 行话中,我们可以做某种柯里化操作,直接向返回的函数发送指令.

path() is a function that wants a string. It returns another function that wants a Directive. And in Scala lingo we can do some kind of currying to directly send a directive to the returned function.

因此块 {} 中的所有内容都被发送到 path() 返回的函数.由于 Scala 中的块总是返回最后一行,我们将返回 get ,它与我们调用 complete 的原则相同.

So everything in the block {} is sent to the function that path() returns. And since a block in Scala always returns the last line we are returning the get which by the same principles we call with complete.

get 也是一个函数,它接受一个参数,可以写成一个块.这相当于只写get(complete("Hello, world")).

get is also a function, that takes one parameter and that can be written as a block. This is equivalent to just writing get(complete("Hello, world")).

再次感谢!

推荐答案

您不一定需要了解此答案中的所有内容才能有效地使用 akka-http,但我向您保证,总会有时间——可能早晚——你将与编译器作斗争,只希望所有花哨的语法糖都消失,好消息是有工具可以让这一切成为可能(坏消息是,一旦你摆脱了花哨的语​​法现实可能是一团糟.

You don't necessarily need to understand everything in this answer to use akka-http effectively, but I guarantee you there will be times—probably sooner rather than later—that you will be fighting with the compiler and will just want all of the fancy syntactic sugar to go away, and the good news is that there are tools that make this possible (the bad news is that once you get rid of the fancy syntax the reality can be a terrifying mess).

首先要注意的是,虽然这里的花括号看起来很像 Java 或其他语言中的作用域或定义分隔符,但它们实际上只是将方法应用于参数.你可以用括号做同样的事情:

The first thing to note is that while the curly braces here may look a lot like scope or definition delimiters from Java or other languages, they're really just applying methods to arguments. You can do the same thing with parentheses:

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 上的静态方法(大约 -阅读全文以了解完整故事),因此以下内容也是等效的:

And while these get and complete things may look like keywords or something, they're really just static methods on Directives (approximately—read this whole thing for the full story), so the following is also equivalent:

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 作为一个非常有用的工具来帮助使这些东西可见.

That hopefully explains some of the syntax, but there's still a lot of invisible stuff going on here. If you're in a REPL, you can use scala-reflect's reify as an extremely helpful tool to help make this stuff visible.

从一个简单(不相关)的例子开始,你可能想知道当你看到像 "a" * 3 这样的 Scala 代码时会发生什么,特别是如果你知道 Java 字符串没有 * 操作符,所以你打开一个 REPL:

To start with a simple (unrelated) example, you might wonder what's happening when you see Scala code like "a" * 3, especially if you know that Java strings don't have an * operator, so you open a 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)

还有脱糖版本,显示了应用于字符串以赋予它* 运算符的隐式方法.

And there's the desugared version, showing the implicit method that's being applied to the string to give it the * operator.

在你的情况下,你可以这样写:

In your case you could write something like this:

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)))))

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

We can reformat the reified expression for readability:

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

如果添加几个导入,这也是完全合法的 Scala 代码:

If you add a couple of imports, this is also perfectly legal Scala code:

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,所以我们知道从 StringPathMatcher 的隐式转换正在开始,并且在我们完全脱糖的版本,我们可以在这里看到:

To explain this step by step, we can start with path("hello"). We can see from the API docs that Directives.path doesn't take a string, but rather a PathMatcher, so we know that an implicit conversion from String to PathMatcher is kicking in, and in our fully desugared version, we can see that here:

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

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

And sure enough if we check the docs, _segmentStringToPathMatcher is an implicit conversion of the appropriate type.

complete("Hello, World!") 中也发生了类似的事情.Directives.complete 需要一个 ToMarshallableResponse,而不是一个 String,所以必须有一个隐式转换开始.在这种情况下它是 ToResponseMarshallable.apply,它也需要一个隐式的 Marshaller 实例,在这种情况下,它通过从 ToEntityMarshallerToResponseMarshallable 的隐式转换获得code>,其中 ToEntityMarshaller 实例是 Marshaller.StringMarshaller,转换器是 Marshaller.liftMarshaller 部分:

A similar thing is happening in complete("Hello, World!"). Directives.complete takes a ToMarshallableResponse, not a String, so there must be an implicit conversion kicking in. In this case it's ToResponseMarshallable.apply, which also requires an implicit Marshaller instance, which in this case it gets via an implicit conversion from a ToEntityMarshaller to a ToResponseMarshallable, where the ToEntityMarshaller instance is Marshaller.StringMarshaller, and the converter is the Marshaller.liftMarshaller part:

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

还记得上面我说过 get 只是 Directives 上的一个静态方法吗?这是一种谎言,因为虽然它是 Directives 上的静态方法,但我们在编写 get(...) 时并没有调用它.相反,这个 get 实际上是一个无参数的方法,它返回一个 Directive0.Directive0Directive[Unit] 的类型别名,而 Directive[Unit] 没有 apply> 方法,它可以通过 Directive 上的 addByNameNullaryApply 方法隐式转换为可以执行的操作.因此,当您编写 get(...) 时,Scala 将其脱糖为 get.apply(...),然后转换 get 值进入 Route =>Route 函数,它有一个合适的 apply 方法.path("hello")(...) 部分发生了完全相同的事情.

Remember how above I said get was just a static method on Directives? That was kind of a lie, in the sense that while it is a static method on Directives, we're not calling it when we write get(...). Instead this get is actually a no-argument method that returns a Directive0. Directive0 is a type alias for Directive[Unit], and while Directive[Unit] doesn't have an apply method, it can be implicitly converted into a thing that does, via the addByNameNullaryApply method on Directive. So when you write get(...), Scala desugars that to get.apply(...) and then converts the get value into a Route => Route function, which has an appropriate apply method. And exactly the same thing is happening with the path("hello")(...) part.

这种事情可能看起来像一场噩梦,作为 Scala 的长期用户,我可以告诉你,它确实经常发生.不过,像 reify 和 API 文档这样的工具可以让它变得不那么可怕.

This kind of thing may seem like a nightmare, and as a long-time Scala user I can tell you that it definitely often is. Tools like reify and API docs can make it a little less horrible, though.

这篇关于Scala 中的函数/方法.这是如何运作的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆