"Action" 这个词在使用 Play 框架的 Scala 函数定义中有什么作用?

What does the word "Action" do in a Scala function definition using the Play framework?

我正在开发 Play 应用程序,并且刚开始使用 Scala。我看到下面的函数中等号之后和大括号之前有这个词 Action

def index = Action {
  Ok(views.html.index("Hi there"))
}

这段代码有什么作用?我见过它与 def index = { 一起使用,但没有与花括号前的单词一起使用。

我假设函数的名称是 index。但我不知道 Action 这个词在这种情况下的作用。

它是 Action 以表达式块作为参数调用。 (apply 方法在后台使用)。

Action.apply({
  Ok("Hello world")
})

一个简单的例子(来自here)如下(看代码中的注释):

case class Logging[A](action: Action[A]) extends Action[A] {

  def apply(request: Request[A]): Result = {// apply method which is called on expression
    Logger.info("Calling action")
    action(request) // action being called on further with the request provided to Logging Action
  }

  lazy val parser = action.parser
}

现在您可以使用它来包装任何其他操作值:

def index = Logging { // Expression argument starts
  Action { // Action argument (goes under request)
    Ok("Hello World")
  }
}

此外,您为 def index = { 提到的案例实际上返回了 Unit,例如:def index: Unit = {.

这个词是 Play Framework 的一部分,它是一个 object,它有方法 apply(block: ⇒ Result),所以你的代码实际上是:

def index: Action[AnyContent] = Action.apply({
  Ok.apply(views.html.index("Hi there"))
})

您的 index 方法 return 是 class Action[AnyContent] 的一个实例。

顺便说一句,您正在将代码块 {Ok(...)} 传递给 apply 方法,该方法(代码块)在这里实际上充当匿名函数,因为 apply的输入不仅仅是Result而是⇒ Result,这意味着它接受一个没有输入参数的匿名函数,即returns Result。因此,当容器收到您的 class Action 实例(来自索引方法)时,您的 Ok 块将被执行,决定执行此块。这只是意味着您只是在此处描述一个动作 - 而不是执行 - 它会在 Play 收到您的请求时实际执行 - 并在路由文件中找到与您的动作的绑定。

此外,您不必在此处使用 def,因为您总是 return 相同的操作 - vallazy val 通常就足够了。仅当您确实想从路由 table 传递一些参数时才需要 def (例如):

GET   /clients/:id          controllers.SomeController.index(id: Long)

def index(id: Long) = Action { ... } // new action generated for every new request here

另一种可能的方法是根据参数选择操作:

def index(id: Long) = {
   if (id == 0) Action {...} else Action{...}
}

但通常你可以使用路由 table 本身,这对于解耦更好。这个例子只是表明 Action 只不过是 return 值。


更新@Kazuya

 val method1 = Action{...} //could be def too, no big difference here

 // this (code inside Action) gonna be called separately after "index" (if method2 is requested of course)
 // notice that it needs the whole request, so it (request) should be completely parsed at the time
 val method2 = Action{ req => // you can extract additional params from request
   val param1 = req.headers("header1")
   ...
 }

 //This is gonna be called first, notice that Play doesn't need the whole request body here, so it might not even be parsed on this stage
 def index(methodName: String) = methodName match {
   case "method1" => method1
   case "method2" => method2
 }

GWT/Scala.js 使用类似的方法进行客户端-服务器交互。这只是解释从路由 table 传递的参数 "methodName" 的重要性的一种可能解决方案。因此,action 可以被认为是函数的包装器,它又代表了对 OOP 方法的引用,这使得它对 REST 和 RPC 都很有用。

其他答案针对您的具体情况。但是,您问的是一般情况,所以我会尝试从这个角度回答。

首先,def 用于定义一个方法not a function(现在最好了解一下区别)。但是,你是对的,index 是该方法的名称。

现在,与您可能熟悉的其他语言(例如 C,Java)不同,Scala 允许您使用表达式定义方法(如使用赋值运算符语法所建议的那样,=).也就是说,= 之后的所有内容都是一个 expression,每次调用该方法时都会将其计算为 value

因此,在 Java 中你必须说:

public int three() { return 3; }

在 Scala 中,你可以直接说:

def three = 3

当然,表达式通常更复杂(如您的情况)。它可能是一段代码,就像您更习惯看到的那样,在这种情况下,值是块中最后一个表达式的值:

def three = {
   val a = 1
   val b = 2
   a + b
}

或者它可能涉及对某个其他对象的方法调用:

def three = Numbers.add(1, 2)

实际上,后者正是您的具体示例中发生的事情,尽管需要更多解释才能理解原因。涉及两个魔法:

  1. 如果一个对象有一个 apply 方法,那么您可以将该对象视为一个 函数。例如,您可以说 Add(1, 2),而您真正的意思是 Add.apply(1,2)(当然,假设有一个带有 apply 方法的 Add 对象)。需要明确的是,它不一定是使用 object 关键字定义的对象。任何具有合适 apply 方法的对象都可以。
  2. 如果一个方法只有一个 by-name 参数(例如,def ifWaterBoiling(fn: => Tea)),那么您可以像 ifWaterBoiling { makeTea } 一样调用该方法。该块中的代码被延迟评估(并且可能根本不被评估)。这相当于写 ifWaterBoiling({ makeTea }){ makeTea } 部分只是定义了一个表达式,它被传入,未计算,用于 fn 参数。