Scala Best Practices: SAY NO TO RETURN


“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” – Martin Fowler

Writing readable and understandable code is often overrated. I feel it’s not big a deal. One should just start following the code conventions right from the start of their career. They’re simple rules just like good habits.

Talking about Scala coding conventions, the most basic practice we often come across is: MUST NOT USE RETURN. But rarely they tell you WHY NOT! So, let’s dig into the world of RETURN and find out why not to use them.

Putting in simple words, return basically transfers the control back to the function. It gives back the result and no statement after the return statement gets executed in that particular function. In scala, the return isn’t required to be written with the resultant value. It evaluates that itself. You must’ve tried writing return explicitly to see if it gives you any error or something. Yes, it wouldn’t flag any error but it has the power to change the intended flow of the program.

Where are we actually returning to

Consider a play method for example

Without return:

def save: Action[AnyContent] = Action {
  if (1 == 2) {
    BadRequest(toJson("something went wrong"))
  } else {
    Ok(toJson(Feature.find))
  }
}

Output: Action(Ok(toJson(Feature.find)))

 With return:

def save: Action[AnyContent] = Action {
  if (1 == 2) {
    return BadRequest(toJson("something went wrong"))
  } else {
    return Ok(toJson(Feature.find))
  }
}

Output: Gives type error

In the first scenario Ok(toJson(Feature.find) is evaluated from the if-else block and since it is wrapped inside Action{} block, hence the method gives up the output asAction(Ok(toJson(Feature.find)))

Now in the second scenario when we write return Ok(toJson(Feature.find)) it is expected to return the value of Ok(toJson(Feature.find)). But as it is going to interrupt the flow of the method it is going to skip the Action{} block and return Ok(toJson(Feature.find))as the output of our save method which is not the expected return type of our method. Hence type error.

We can say that return statement does not return you from the enclosing block or anonymous function but it returns you from enclosing method(def).

Return type ‘Not’ inferred

Another drawback is that the return type can’t be inferred. You have to explicitly write the return type for any method. This is one of the advantages of Scala that it infers the return type of the methods. But with return, it doesn’t work.

def someString = return "some string"
Output: compiling error
def someString: String = return "some string"
Output: some string

Non-LocalReturnControl

When we use the return statement inside anonymous functions its implementation is done by throwing and catching (Although it not true always) an object of NonLocalReturnControl which is a child of Throwable and also inherits NoStackTrace(wonder from where you got the error). Hence it has some kind of overhead over the normal flow. It does not usually create problems. But there’s always a chance that if we are handling our errors we may catch this exception too in our stack trace. So better to leave out the return when using error handling.

A code sample just to test return in anonymous function

In Scala:


class Person(name: String){
    def returnMethod(i: Int): Int = {
        for(i <- Range(1,10)){
            return i
        }
        10
    }
}

Decompiled Version:

Here in the decompiled version, we can see that the implementation of the return statement is actually done by catching the NonLocalReturnControl exception.

public class Person {
    public int returnMethod(int i2) {
        int n;
        Object object = new Object();
        try {
            package$.MODULE$.Range().apply(1, 10).foreach(i -> Person.$anonfun$returnMethod$1(object, BoxesRunTime.unboxToInt((Object)i)));
            n = 10;
        }
        catch (NonLocalReturnControl ex) {
            if (ex.key() == object) {
                n = ex.value$mcI$sp();
            }
   throw ex;
        }
        return n;
    }

    public static final /* synthetic */ Nothing$ $anonfun$returnMethod$1(Object nonLocalReturnKey1$1, int i) {
        throw new NonLocalReturnControl.mcI.sp(nonLocalReturnKey1$1, i);
    }

    public Person(String name) {
    }
}

Return is not referentially transparent

An expression is said to be referentially transparent if it can be replaced with its corresponding value without changing the program’s behavior. As a result, evaluating a referentially transparent function gives the same value for same arguments. And Return is not so. Let’s see this through code:

Simple return:

 def condition(int: Int): Int = {
  if(int > 30){
    int - 30
  } else {
    return int
  }
}

Perfectly works

Referenced return:

def condition(int: Int): Int = {
  val result = return int
  if(int > 30){
    int - 30
  } else {
    result
  }
}

Doesn’t execute anything after 2nd statement (as the flow breaks with return)

Type of Return Expression

We know that the returned value should conform to the expected type mentioned in the method declaration. But what’s the type of the return statement? Let’s explore it by trying it with different types.

1.Trying Int:

def condition(int: Int): Int = {
 val result: Int = return int;
 2
}

It perfectly works!

Safe to assume that the type of return statements may be the type of the returning value.

2. Trying String:

def condition(int: Int): Int = {
 val result: String = return int;
 2
}

No issues

So works with string too!!!

3. Trying Nothing:

def condition(int: Int): Int = {
 val result: Nothing = return int;
 2
}

By this point, it is not a surprise anymore.

And if you use IntelliJ to auto check for the return type it gives ‘Nothing’. So the type of return statement is Nothing.

Still need it? No, you don’t 😀

Well, after mentioning the worse it could do to your code if you still feel that you need to use it. PLEASE THINK AGAIN. You can easily change the way of your computations.

It might take your time and efforts to get used to this good practice, but it’s always good to write computations that terminate properly than to reason out things that went wrong.

Follow best practices! Happy coding 🙂

References:

https://tpolecat.github.io/2014/05/09/return.html


knoldus-advt-sticker


Advertisements
This entry was posted in Scala. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s