Scala Tutorials Part #3 - Methods
Originally Posted On : 03 Sep 2016 Last Updated : 10 Jan 2017
Methods in scala
It has been a long time since I wrote the last article on scala which was back in December 2015. Hopefully I should be writing more
This is part 3 of the scala tutorial series. Check here for the full series.
This entire post is about dealing with methods in scala. Most of them are about style, but very important to know as we get into the world of functional programming.
- Syntax of methods
- The Unit type
- Immutable method parameters
- Notes on the return keyword
- Heterogeneous return types
- Call by name vs Call by value
- Default values for method parameters
- Stubbing out methods
Back in the days of assembly programming, there was something called sub-routines
The same ideas evolved to what methods are today. A simple way to organize programs into little chunks of code that do something unique.
Sub-routines, Procedures , Functions, Methods may or may not mean the same thing and it is really hard to give a generalized difference since has different meanings depending upon the programming language under consideration.
In scala, we care only about Functions and Methods.
There are several ways of declaring and using methods. Below are examples just to get familiar with the syntactic sugar.
- Method which takes two parameters and returns a value
- Method without curly braces
- Method which does not take any parameters
- Method with no return type, parameters
- Method with return type not mentioned
Above is a simple method that takes in two variables x,y and returns the bigger variable among the two.
All methods start with the
def keyword, followed by the method name. Then comes the optional parameters and return type.
How do x and y get returned ? Hold on to that question , we will get there.
Similar to java, method declarations come within the curly braces, but this is optional.
This is purely for simplicity’s sake, when the method is smaller then we can ignore the braces. But when it gets big, it is better to put inside a curly brace code block.
As mentioned before, method parameters can be optional.
Note that I have omitted the circular braces for readablity. It can also be empty as below.
Prints a hello world message to the console.
An important point about declaring in this way is the calling code cannot use something like
printHelloMsg(), it can only call by using
printHelloMsg i.e without the circular brace.
When calling it with the circular brace, it means that we are passing in a empty parameter rather than no parameter.
This is called a 0-arity method which is explained in the method invocation docs
This returns “Hello” concatenated with whatever string that you passed in.
In all of the examples, wherever type information is required, scala uses type inference.
One thing to note is that if we omit the
= symbol, the compiler treats it as a
Unit type method even if we return something from that method.
In functional programming terms a method which does not return any value i.e which returns
Unit are called procedures. These are methods which have no side effects and
are independent of state.
If we run the below example in the scala REPL,
We can see that the method type is of Unit.
An equivalent example of the above method in java would be something like below.
This should gives us an idea about the Unit type. But is it actually equivalent to void?
They are similar, but not the same.
Unit is actually a class in the type system hierarchy in scala. Below example should illustrate why this difference is important.
If we call this method, nothing gets printed/returned.
The compiler will throw a warning such as below.
warning: enclosing method someMethod has result type Unit: return value discarded
Assigning the return type to a variable will give an output as
(). Can be understood from the runtime boxed unit which is called from Unit.scala unboxing. This is nothing but a
toString representation of the type
By default method parameters that are passed into methods are immutable and cannot be changed. The below image shows an example.
Remember that scala embraces immutability more than java. The equivalent java method would be something like
Re-writing it to not change the method parameters is pretty straighforward, but the reason why it is designed such a way is again because of functional programming and getting rid of mutable state and favoring immutable objects.
We will learn more about immutability once we start to explore the functional programming side of scala.
The scala compiler is built in a way to take the last statement and return it in the absence of a return statement.
But, in situations where the last statement is not intended to be the output of the method, then the results could be different from what we might expect.
We can call this method with whatever integer parameters, it would return
Some String. Remember that scala has limited type inference and in this particular situation this is the best it can come up with.
If you are using an IDE, you can see a warning on hovering over these variables.
Which expands to “Highlights expression if it has no side effects and does not affect on any variable value or function return. It may be either removed or converted to a return statement ”
This tells us that the statements have no effect since we have a string variable declared below.
If we use the same code, now with the return keyword, the compiler promptly complains.
The reason for this error is because, we are immediately cutting down the flow of execution and then returning from that point. If the we were to infer the type, it would involve traversing the entire call stack, so the compiler resorts to a safer way of making the programmer explicitly mention the return type.
Here is one more example.
This would return
a if it is greater than
b, else it would return the
Unit type deduced at compile time.
Notice that the method return type is
AnyVal, since the
else part is
Unit and the
if part is an
Int, it promotes to the next available type available in the hierarchy.
We can infer the following points from our analysis.
- Sub-typing is used to promote types up the order.
- If you use the
returnkeyword, you must mention type compulsorily.
- The last statement will be used for the return type, if you give a wrong type, then the compiler will not be able to identify it.
- All type errors are in compile time, hence full type safety is ensured.
- Types of method parameters should always be mentioned. Since scala uses local type inference, it will not be able to deduce them at compile time.
When doing pure functional programming you would not use the return keyword at all.
Avoiding the return keyword might seem bizarre at first, but will sink in once we learn more about functional programming.
Sub-typing enables us to do a lot of things. One of them is mixing up types in heterogeneous way.
Consider the below example.
The type qualifies to the
Any type, which is at the top level. If you are a Java programmer then you would quickly use
instanceof to guess the type.
instanceof is an anti-pattern and considered bad programming practice. In scala you would use the Either type. But that is a topic
of another blog post.
When you are calling methods, it is imperative to understand how the compiler treats it. In scala, there are two ways a method can treat its parameters.
- Call by value
- Call by name
Call by value is the regular execution strategy where the value is reduced before it is evaluated, where as in Call by name the expression is not evaluated until it is used somewhere in the code.
If we call the method by
multiply(6 + 4,3), below is what happens.
Note : The values inside the method name depict the computations and not calls to the actual method.
Call by value -> multiply(10,3) -> multiply(10 * 10) -> 100
Call by name -> multiply(6+4,3) -> multiply(10,3) -> multiply(10 * 10) -> 100
In call by name, the expression
6+4 is evaluated only after it enters the call stack, whereas in call by value it is evaluated beforehand.
Alternatively, if we call using
Call by value -> multiply(10,7) -> multiply(10 * 10) -> 100
Call by name -> multiply(10*10) -> 100
Call by name is shorter in this case, since the second variable is not evaluated at all as it is not necessary.
We can see that both strategies yield the same result.
Scala uses Call by value strategy by default because in a general use case it is better than its counterpart, but also supports Call by name if forced.
Below is a real world example.
=> is used to depict that the variable should be called by name.
If we debug the values using intellij, then we can see that callByValue has the variable
value computed to 4 already.
But whereas in callByName it is not.
Both the examples lead to the same result if executed, the difference is in the way the parameters are called.
The example makes it even more clearer on when the value is evaluated.
Ok, but in what situations we need to call by name and where do we need to call by value ?
That question requires further understanding of functional programming which will be dealt with in the following posts. Usually you would call by value, but there are situations where call by name would be a better choice.
When declaring methods, we can also specify the default value of the parameters in case the caller does not provide any.
The value of default gets assigned to the string
url in the case of value not provided.
A general widely used programming practice is to declare methods as stubs i.e without their logic implemented. There might be several reasons where one would choose to do this and it depends upon the developer to use it at the right places.
In java you would typically use
java.lang.UnsupportedOperationException to add stubbed methods, which is kind of
obscure, given that a missing implementation is presented as unsupported operation.
??? to indicate that the method is not yet implemented which is declared as below.
When calling these methods we can see a more clearer stack trace exception.
Exception in thread "main" scala.NotImplementedError: an implementation is missing at scala.Predef$.$qmark$qmark$qmark(Predef.scala:230) at Child.getAge(Child.scala:7) at Runnable$.delayedEndpoint$Runnable$1(Runnable.scala:8) at Runnable$delayedInit$body.apply(Runnable.scala:4) at scala.Function0$class.apply$mcV$sp(Function0.scala:34) at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12) at scala.App$$anonfun$main$1.apply(App.scala:76) at scala.App$$anonfun$main$1.apply(App.scala:76) at scala.collection.immutable.List.foreach(List.scala:381) at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35) at scala.App$class.main(App.scala:76) at Runnable$.main(Runnable.scala:4) at Runnable.main(Runnable.scala)
Most of what I have explained here is about understanding the object oriented side of scala. In my experience it is better to understand this side first and then step into functional programming.
Stay tuned for the my next article on Objects and Classes.