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:

  1. What is a method?
  2. Where do method parameters go in a method definition?
  3. What's the difference between a parameterless method and a value?
  4. Define a method using a code block.
  5. Define a method named subtract that will subtract two values and give back the result of the calculation.
  6. Write a function named add7 that takes one number and returns the result of adding 7 to it.
  7. Write a function named multiply that takes two numbers and returns their product.
  8. Write a function named lastLetter that takes a String and returns the last letter of said String. (Hint: look at the helpful String methods). lastLetter("abcd") should return "d"
  9. Are method definitions statements or expressions?
  10. 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.

  1. What is a method? A method is a reusable piece of code given a name.
  2. Where do method parameters go in a method definition? They go between the parentheses.
  3. 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.
  4. Define a method using a code block.
    def add(a: Int, b: Int) = 
    a + b
    
  5. 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
    
  6. Write a function named add7 that takes one number and returns the result of adding 7 to it.
    def add7(a: Int) = a + 7
    
  7. Write a function named multiply that takes two numbers and returns their product.
    def multiply(a: Int, b: Int) = a * b
    
  8. Write a function named lastLetter that takes a String and returns the last letter of said String. (Hint: look at the helpful String methods
    def lastLetter(string: String) = string.last
    
  9. Are method definitions statements or expressions? Method definitions are statements
  10. 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.

Did you find this article valuable?

Support Mark Hammons by becoming a sponsor. Any amount is appreciated!