8

I am learning Scala at the moment. One thing that I like to do is early returns. I'm convinced that this is far easier for everyone to read as we just remove the invalid states before. Now, as Scala is a functional language and I've read that cutting computation is bad functional style, I'm wondering if there is some trick or functional programming equivalent to early return.

This is code I would write, to be completely clear, this is just a dumb example, I'm not looking for the special hack of my special case, but more for a general advice on how to deal with these.

if (request.headers.get(session_header).isEmpty) {
  BadRequest(session_not_set)
} else {
  Ok(CartHelper.getCart(session, user))
}

Now, what I'm tempted to do is:

if (request.headers.get(session_header).isEmpty) {
  BadRequest(session_not_set)
  return;
}

Ok(CartHelper.getCart(session,user))

If you have any hint for me!

Martin GOYOT
  • 286
  • 1
  • 4
  • 12
  • 4
    https://tpolecat.github.io/2014/05/09/return.html – Michael Zajac Aug 08 '16 at 16:39
  • The errors you're encountering are because you are trying to use `return` within an anonymous function (within a Play `Action`), which returns from the entire method instead of the anonymous function. In this case it is _impossible_ to do what you want, but I also encourage you to read the above article, as early returns are not good practice. – Michael Zajac Aug 08 '16 at 16:44
  • Early return is usually used when replacing it with `if ... else ...` ends up with wildly different in size clauses. Simple solution is to move the bigger one to a separate method. – Victor Moroz Aug 08 '16 at 17:01
  • Just for the duplicate: As I explained in my post this is just a dumb example, it has nothing to do with play, it is a general how to or more not to early return in Scala/Functional programming – Martin GOYOT Aug 08 '16 at 19:10

1 Answers1

5

In some instances the return keyword cannot be avoided, but it doesn't look like you have that problem currently.

Scenario 1: The single condition scenario, your current one. In this instance you can avoid using return with a very simple if else.

def doSomething: AnyContent = {
  if (request.headers.get(session_header).isEmpty) {
    BadRequest(session_not_set)
  } else {
    Ok(CartHelper.getCart(session,user))
  }
}

If the session not being set is a common problem, you can simply have a guard around it with a partial function.

def requireSession(req: Request)(
   pf: Session => AnyContent
): AnyContent = {
   request.headers.get(session_header)
     .fold(BadRequest("Session not set"))(pf(_))
}

And then:

// Assuming Play framework being used here
def getCart: AnyContent = Action { implicit req =>
  requireSession(req) { session => Ok(CartHelper.getCart(session, user) }
}

Scenario 2: Break loop using return, or the so called early return is usually a performance improvement element.

One apparently valid use of return in Scala which is something that seems unavoidable is a situation where you are iterating a collection for the first of something. Obviously you can have that abstracted away for you using collection.find and other helper methods pre-build in the standard lib, but for the sake of argument.

def inList[T](l: List[T], value: T): Boolean = {
  for (el <- l) {
    // break the loop for the first match found
    // for the sake of efficiency.
    if (el == value) return true;
  }
  false;
}

Even in situations like this return is avoidable, by using a different construct, and there's always a recursive version of something you can use to replace an apparently impossible return inside dan iteration.

flavian
  • 28,161
  • 11
  • 65
  • 105
  • 2
    Using `return` can _always_ be avoided. https://tpolecat.github.io/2014/05/09/return.html – Michael Zajac Aug 08 '16 at 16:39
  • I agree and I know the post you're referring to, this was just more geared at someone who's new to Scala. – flavian Aug 08 '16 at 16:40
  • **Scenario 2**: tail-recursion if it doesn't fit into `find`, or a simple `while` loop. Return from `map`/`flatMap` is a bad thing always (that's what `for` is converted to). – Victor Moroz Aug 08 '16 at 16:50
  • 1
    For the first part of scenario 1, this is what I have right now, and this is a question of point of view, but in my opinion having a else is more difficult to read than an early return, hence my original question. And I see the art of coding as writing code for your colleagues, not for the computer. This said, I really like the second part of the scenario which looks clean and functional. Could you try to explain me further what is the role of `implicit` here? I have a hard time wrapping my head around this and why I couldn't just put `Action { req =>...` – Martin GOYOT Aug 09 '16 at 08:08
  • @MartinGOYOT It's because Play uses the request object in scope to pass through a bunch of different properties, such as the `Messages` instances and things like that. When you make the request implicit, inside the `Action` block anything that uses `def fn()(implicit req :Req)` can now access that request object, and play just makes use of that internally. Instead of typing `Messages.preferred(req)` you can simply use stuff like `Messages("my.title")`, and internally that probably looks like `def apply(str: String)(implicit req: Req) = Messages.preferred(req).find(str)`. Hope this helps. – flavian Aug 09 '16 at 08:18
  • @MartinGOYOT Messages are just one example, it also passes through a `RequestHeader` instance and more. Early `return` is not clean at all in that instance, and it may be a lot more inefficient than good old compiler branching which can be easily optimised by the compiler. – flavian Aug 09 '16 at 08:21
  • I guess I have to dig more into what is implicit. For the early return not being clean once again it is for readability purpose, I'm not talking compiler branching or any performance stuff. But this is me having a very limited functional programming experience and that's why I wanted to have the functional way of dealing with this. Thanks for your answer, I think I'll go with the scenario 1 part 2 which I like the most. – Martin GOYOT Aug 09 '16 at 08:31
  • I'm sorry but I still don't get why, in the Scenario 1, I would need to make req implicit. In your Messages example I understand that req not being passed directly to the function we have to look for it in the scope hence the implicit in the function signature but why should it be implicit at the action level? Plus in Scenario 1 it is passed everywhere explicitly. For instance in my code right now I have `def get = Action { request =>...` and inside the block I use the request parameters and it works. What is the need to have it implicit? – Martin GOYOT Aug 09 '16 at 08:52
  • @MartinGOYOT I just did it by reflex, if you don't need it just don't add it. – flavian Aug 09 '16 at 09:53
  • Ok so that clarifies this part, thanks. But what is the gain to put it as an implicit value as it is explicitly passed by the play framework which does something like `block(request)` for the action. – Martin GOYOT Aug 09 '16 at 10:02
  • I just explained that above, the user doesn't need to touch it. – flavian Aug 09 '16 at 10:08
  • Okay, so if I get it correctly; now if I put it as implicit, I could change `requireSession` to something like `def requireSession(implicit request: Request)( pf: Session => AnyContent ): AnyContent =...` and use request inside this and remove it from the call of `requireSession` from the Action, so I would do `requireSession { session => Ok(CartHelper.getCart(session, user) }` instead of `requireSession (req) {...` – Martin GOYOT Aug 09 '16 at 10:21
  • Yes, that's the point of using implicits, except `requireSession` would probably look like this: `def requireSession(pf: Session => AnyContent)(implicit request: Request): AnyContent` otherwise the compiler would obviously think the `pf` is the `req` when you do `requireSession { session => }` – flavian Aug 09 '16 at 10:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/120502/discussion-between-martin-goyot-and-flavian). – Martin GOYOT Aug 09 '16 at 10:31
  • @MartinGOYOT Go read about implicits, unfortunately I cannot spend more time here. This hasn't got anything to do with the initial question and has already been answered here: http://stackoverflow.com/questions/10375633/understanding-implicit-in-scala – flavian Aug 09 '16 at 10:33