Explicit return from play action

2019-01-20 06:59发布

问题:

I have following action

def login: Result = Action(parse.json) { request =>
  if (/* Let's we say here is some validation */) {
    return BadRequest("bad")
  }

  Ok("all ok")
}

This is giving me an Error. Intellij is suggesting Action[JsValue] type. When I say return will be of that type, I am getting again error on BadRequest line, because types doesn't match.

I tried searching about this problem, and I found some answers which are suggesting to set Action[AnyContent] as return type. But I am STILL getting error.

Also I need to return from if...I don't want to write else after that if, because in some more complex function most probably I will have few if statements which should break action, and if I use if/else approach, code will be nightmare.

回答1:

Of course it does. In Scala, a "return" statement inside a nested anonymous function is implemented by throwing and catching a NonLocalReturnException. It says so in the Scala Language Specification, section 6.20.

This is done because of for comprehensions, for people to be able to write code like this:

def loop() = {
  for (i <- 0 until 20) {
    if (someCondition) return
  }
}

That loop is actually equivalent to:

(0 until 20).foreach { i => if (someCondition) return }

Can you see the anonymous function? Can you see that the "return" in question doesn't refer to returning from that anonymous function alone? In my opinion this was a design mistake. On the other hand, in a language like Scala, "return" isn't needed anyway.

So you have an anonymous function in there:

Action(parse.json) { request => ... this one here ... }

And inside that function you're using "return" which under the hood triggers an exception.

So the general rule of thumb in Scala - NEVER, EVER USE RETURN. It's an expression-oriented language anyway. You don't need it.

Action(parse.json) { request =>
  if (/* Let's we say here is some validation */) 
    BadRequest("bad")
  else
    Ok("all ok")
}

There, much more idiomatic. BTW - you also don't have "break" or "continue". Get used to working without them. Also, about your opinion:

Also I NEED to return from if...I don't want to write else after that if, because in some more complex function most probably I will have few if statements which should break action, and if I use if/else approach, code will be nightmare.

That is wrong and I REALLY HATE working with code that relies on return, break or continue for short-circuiting the logic PRECISELY because complex logic gets messy and I want to have a clear view of:

  1. invariants, if we are talking about a loop
  2. exit conditions
  3. the branches / paths

Using return, break or continue destroys clarity on all 3 points. They aren't much better than GOTO jumps. And if you have complex functions, break them into multiple functions if reading it starts to become a problem.

Also, much better than multiple "if/else" branches are "match" statements. Once you get used to them, you'll love them, especially given that the compiler can even protect you in some cases in which you're missing a branch.



回答2:

You cannot use return in this context because the Scala compiler misinterprets what you mean by return. You intended for the return to return from the function that is being passed to Action.apply, i.e. this block:

{ request =>
  if (/* Let's we say here is some validation */) {
    return BadRequest("bad")
  }

  Ok("all ok")
}

However, in Scala return cannot be used to return from functions, only methods. Any return is interpreted as returning from the nearest enclosing method. In this case, the compiler thought you were returning from login.

You have the wrong type on login. login should be a Action[JsValue]. With that correct type, the compiler will complain about the return because you are returning a SimpleResult instead of the expected Action[JsValue]. With the incorrect type Result, the compiler accepts the return but then isn't happy because the return value of Action.apply is always a type of Action.

The reason return works this way is to support loops like:

def allPositive(l: List[Int]): Boolean = {
  l.foreach { x => if (x < 0) return false }
  true
}

But using return is unidiomatic. The return in the above loop is implemented with exception throwing so it is inefficient. And return can make for very confusing code:

def login: Result = Action(parse.json) { request =>
  /*
  WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE
  WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE
  WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE
  WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE
  WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE
  WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE
  WALL OF CODE WALL OF CODE WALL OF CODE WALL with return  OF CODE
  WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE
  WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE
  WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE WALL OF CODE
  */

  Ok("all ok")
}

Obviously this login always returns Ok. Oh wait...there is a return buried in there.