Defining Methods
In this unit, we'll cover how to define methods, why you would want to, and how methods are similar and dissimilar to values.
Please remember to run the provided examples via the Scala console unless told otherwise. If you've forgotten how to launch and/or shut down the Scala console, please refer here.
Defining methods
Sometimes we have some complex piece of code, and we don’t want to write it over and over again. In order to do this, we employ a form of shorthand called methods that allows us to reuse complex code without having to write said code over and over again. We've used methods in a previous unit, but never defined our own. Defining your own methods allows you to reuse code and adapt it to new situations. Method definition takes the following form:
def <name>(<name>: <type>, <name>: <type>): <type> = <expression>
Method definition always starts with the def
keyword. This keyword signals to Scala that you're starting a method definition statement. Following the def
keyword and a space, you provide the name of the method. Method names (and all names in Scala) follow the same rules as values names.
Following the name, you start a section with parentheses that indicates the inputs for the method (what we call parameters). These parameters are specified by providing a name (the naming rules apply) followed by a type. Between each parameter, you put an ,
to seperate them, and once you're done specifying parameters, you can close the parentheses to end the parameters section of the method definition.
You can follow the parentheses with :
and a type to indicate what kind of data the method will give back, but just like with values, Scala can infer this, so it's not necessary. Finally, just like with values you use the assignment operator =
followed by an expression that defines the work the method does. We call this expression the "body" of the method.
Let's look at a practical example of a method definition in Scala:
scala> def add(a: Int, b: Int) = a + b
def add(a: Int, b: Int): Int
Here we've defined a method named "add". The definition specifies two parameters, "a" and "b" which both have the type Int
. We don't specify the return type of this method, but rather let Scala figure it out from the method body a + b
. Since a
and b
both have the type Int
, and we use the +
operator, the body of "add" has a result type of Int
Invoking the add method involves using its name, along with parentheses that contains two Int
expressions.
scala> add(5,10)
val res0: Int = 15
The expressions passed into the parameters effectively replace a
and b
in the method's body, and the method's body effectively replaces the add invocation. You can imagine that the entire expression add(5, 10)
is replaced by 5 + 10
.
This add
method is simple, but it's not very useful. The code it replaces is not much more complex than the code we need to use it. A more fitting example would be a method to calculate a person's BMI.
The formula to calculate BMI is kg/m2. In Scala you would write this like kg/(m*m)
. Writing this formula out over and over is more than annoying; writing it with plain numbers tosses away information about what your program is doing.
scala> 100.0/(2.0*2.0) //what is the result number here supposed to indicate?
val res0: Int = 25.0
Defining a BMI function will not only save us some keystrokes, it will let us know that we're calculating BMI (and give us context about what 100.0
and 2.0
are).
scala> def bmi(meters: Double, kg: Double) = kg / (meters * meters)
def bmi(m: Double, kg: Double): Double
scala> bmi(2.0, 100)
val res0: Double = 25.0
Method bodies
As noted above, the body of a method is an expression. Any expression you wish in fact, and this includes code blocks with statements in them. Therefore, the following code is perfectly valid:
scala> def printBmi(meters: Double, kg: Double) =
def square(a: Double) = a * a
val bmi = kg/pow(meters)
println(bmi)
def printBmi(meters: Double, kg: Double): Unit
scala> printBmi(2.0, 100)
25.0
Scala is fairly flexible, so it will allow you to define methods within method definitions within method definitions within method definitions if you like.
Return types
When your method is invoked, it gives back data. We call the type of this data the return type. Just like with values, this type can be inferred by the Scala compiler based on the definition of the method body. In our add
method, the return type is clearly Int
, since Int + Int
gives back an Int
. You can, however, specify the return type if you please.
scala> def add(a: Int, b: Int): Int = a + b
def add(a: Int, b: Int): Int
To specify the return type, just use :
after the parentheses, followed by the type of data your method will give back.
Method Parameters
Method parameters are like values that exist only within your method. You can have no parameters, or you can have many. Each parameter has to have a unique name, and has to have a type associated with it. Scala requires explicit types for parameters to help you make sure that your method is validly written. Let's imagine the add method again, without types for the input parameters:
def add(a, b) = a + b
Now, Scala could be made so that this works as long as a and b have a +
operator defined for them. This is called structural typing. But we already know that String
has a +
operator defined for it:
scala> "a" + "b"
val res0: String = ab
scala> "b" + "a"
val res1: String = ba
This does not obey the rules of add. Add should be commutative, but String
's +
is not commutative. This means our add
method would behave slightly wrong for certain data, and this may well surprise the users of our add
method. This problem is minor right now, but gets worse as your methods gets more complex. It's for this reason that Scala forces you to define the input types. It helps you make certain your method behaves the way you want it to across the valid input ranges it accepts.
Another thing to keep in mind when using method parameters is order. When passing input to a method, you need to make sure you pass the right input in the right place, or your function could mess up. Take the bmi function again as an example:
scala> bmi(100, 2.0)
val res0: Double = 2.0E-4
The first parameter on bmi is meters, and the second is kilograms. Unless the person in question is the tallest person on earth, and lighter than a laptop, the inputs above were passed in in the wrong order. You can avoid this problem if you remember the parameter names by using them when invoking the method:
scala> bmi(kg = 100, m = 2.0)
val res0: Double = 25.0
Parameterless methods
You can choose to make methods that do not take parameters. In order to do this, you can write the method two ways:
def <name>(): <type> = <expression>
or
def <name>: <type> = <expression>
Here's some examples:
scala> def method1() = "I don't take parameters"
def method1(): String
scala> def method2: String = "me neither"
def method2: String
These two are fairly similar in how they behave in your program. If you include empty parentheses at the end of the name, you'll have to use empty parentheses to invoke the method. If you don't, you can just use the method name to invoke the method.
scala> method1()
val res0: String = I don't take parameters
scala> method2
val res1: String = me neither
You might notice that a val definition and a method definition with no parameters and no ()
at the end look incredibly similar. And they actually are in Scala. They can be used interchangeably in some situations. However, some code can reveal the difference between parameterless methods and values:
scala> def method3 = println("I have side effects")
def method3: Unit
scala> val a = println("I have side effects")
I have side effects
scala> println(a)
()
scala> println(a)
()
scala> println(method3)
I have side effects
()
scala> println(method3)
I have side effects
()
Even though method3
looks like a value, it doesn't behave like one. Each time you invoke it, its body is evaluated and you print text to the console. On the other hand, a similarly defined value a
will only print to the console once, when its definition is evaluated while it's being created.
Practice
Try to answer the following questions about methods:
- What is a method?
- Where do method parameters go in a method definition?
- What's the difference between a parameterless method and a value?
- Define a method using a code block.
- Define a method named
subtract
that will subtract two values and give back the result of the calculation. - Write a function named
add7
that takes one number and returns the result of adding 7 to it. - Write a function named
multiply
that takes two numbers and returns their product. - Write a function named
lastLetter
that takes aString
and returns the last letter of saidString
. (Hint: look at the helpful String methods).lastLetter("abcd")
should return"d"
- Are method definitions statements or expressions?
- Are method invocations statements or expressions?
If you're having trouble, you can ask questions in the discord channel I've set up for teaching Scala, or you can discuss on scala-users.
Answers
Please read through and attempt the practice section before reading these answers. Practicing and trying to answer the questions on your own will help cement your knowledge.
- What is a method? A method is a reusable piece of code given a name.
- Where do method parameters go in a method definition? They go between the parentheses.
- What's the difference between a parameterless method and a value? A parameterless method re-evaluates the code that defines it each time its name is invoked. A value only evaluates the code that defines it once, when it is first defined.
- Define a method using a code block.
def add(a: Int, b: Int) = a + b
- Define a method named
subtract
that will subtract two values and give back the result of the calculation.def subtract(a: Int, b: Int) = a - b
- Write a function named
add7
that takes one number and returns the result of adding 7 to it.def add7(a: Int) = a + 7
- Write a function named
multiply
that takes two numbers and returns their product.def multiply(a: Int, b: Int) = a * b
- Write a function named
lastLetter
that takes aString
and returns the last letter of saidString
. (Hint: look at the helpful String methodsdef lastLetter(string: String) = string.last
- Are method definitions statements or expressions? Method definitions are statements
- Are method invocations statements or expressions? Method invocations are expressions
Summary
Methods help us in a number of ways. They reduce the complexity of our code by letting us use shorthand for a section of code. They also help us document what our program is doing by naming functionalities. If you wish to learn Scala, mastering methods is a must. It's my hope that this unit has been helpful to you. Next unit we'll cover if-expressions and the concepts of diverging paths of execution in your program.