Learn About The Basic Functions and Methods in Scala

If You are interested to learn about the Scala Loop Statements

A function is a group of statements that perform a task. You can divide up your code into separate functions. How you divide up your code among different functions is up to you, but logically, the division usually is so that each function performs a specific task.

Scala has both functions and methods and we use the terms method and function interchangeably with a minor difference. A Scala method is a part of a class which has a name, a signature, optionally some annotations, and some bytecode where as a function in Scala is a complete object which can be assigned to a variable. In other words, a function, which is defined as a member of some object, is called a method.

A function definition can appear anywhere in a source file and Scala permits nested function definitions, that is, function definitions inside other function definitions. Most important point to note is that Scala function’s name can have characters like +, ++, ~, &,-, –, \, /, :, etc.

Function Declarations

A Scala function declaration has the following form −

def functionName ([list of parameters]) : [return type]

Methods are implicitly declared abstract if you don’t use the equals sign and the method body.

Function Definitions

A Scala function definition has the following form −

Syntax

def functionName ([list of parameters]) : [return type] = {
   function body
   return [expr]
}

Here, return type could be any valid Scala data type and list of parameters will be a list of variables separated by comma and list of parameters and return type are optional. Very similar to Java, a return statement can be used along with an expression in case function returns a value. Following is the function which will add two integers and return their sum −

Syntax

object add {
   def addInt( a:Int, b:Int ) : Int = {
      var sum:Int = 0
      sum = a + b
      return sum
   }
}

A function, that does not return anything can return a Unit that is equivalent to void in Java and indicates that function does not return anything. The functions which do not return anything in Scala, they are called procedures.

Syntax

Here is the syntax –

object Hello{
   def printMe( ) : Unit = {
      println("Hello, Scala!")
   }
}<a href="https://www.tutorialspoint.com/scala/scala_loop_types.htm"></a>

Calling Functions

Scala provides a number of syntactic variations for invoking methods. Following is the standard way to call a method −

functionName( list of parameters )

If a function is being called using an instance of the object, then we would use dot notation similar to Java as follows −

[instance.]functionName( list of parameters )

Try the following example program to define and then call the same function.

Example

object Demo {
   def main(args: Array[String]) {
      println( "Returned Value : " + addInt(5,7) );
   }
   
   def addInt( a:Int, b:Int ) : Int = {
      var sum:Int = 0
      sum = a + b

      return sum
   }
}

Save the above program in Demo.scala. The following commands are used to compile and execute this program.

Command

\>scalac Demo.scala
\>scala Demo

Output

Returned Value : 12

Scala functions are the heart of Scala programming and that’s why Scala is assumed as a functional programming language. Following are few important concepts related to Scala functions which should be understood by a Scala programmer.

Functions Call-by-NameFunctions with Named Arguments
Function with Variable ArgumentsRecursion Functions
Default Parameter ValuesHigher-Order Functions
Nested FunctionsAnonymous Functions
Partially Applied FunctionsCurrying Functions

Anonymous Function

An anonymous function is one without a name or an explicit handle. This can be useful when we need to provide a piece of code or action as a parameter to another function.

Let’s see an example:

(number: Int) => number + 1Copy

On the left side, in parentheses, we define function parameters. After the => sign, we define an expression or list of statements.

The parameter list can be empty (), or we can define as many parameters as we need:

() => scala.util.Random.nextInt
(x: Int, y: Int) => (x + 1, y + 1)Copy

To define more than one statement, we must enclose them in curly braces. The result of a final expression in a block of code is a function’s return value.

Scala has a return keyword, but it’s rarely used:

(number: Int) => {
    println("We are in a function")
    number + 1
}Copy

In the example above, we defined a function that takes one parameter named number. The function body has two expressions. The last expression increases the parameter value by one and returns the result.

The type of result is inferred automatically — argument number is of type Int, and after adding 1, the result is also an Int.

Named Function

Everything is an object in Scala, so we can assign a function to a value:

val inc = (number: Int) => number + 1Copy

Value inc now contains a function. We can use this value everywhere we need to call the unit of code defined in function:

scala> println(inc(10))
11Copy

Thus, inc is considered a named function.

There is some “magic” behind-the-scenes that Scala does to allow the assignment of a function to a variable. All functions in Scala are special objects of type Function1 or Function2 or FunctionN, where N is the number of input arguments to the function.

The compiler will box the function code we provide into the appropriate object automatically. This object contains a method apply() that can be called to execute our function.

Furthermore, parentheses are “syntactic sugar” for calling a function’s apply() method. So, these two statements are equivalent:

scala> println(inc(10))
11
scala> println(inc.apply(10))
11

Methods

Methods are essentially functions that are parts of a class structure, can be overridden, and use a different syntax. Scala doesn’t allow us to define an anonymous method.

We have to use a special keyword def for method definition:

def inc(number: Int): Int = number + 1Copy

We can define a method’s list of parameters in parentheses after its name. After the parameters list, we can provide a return type for the method with the leading colon sign.

We then define a method’s body after the equals sign. The compiler allows us to skip the curly braces if there is only one statement of code.

Method definition allows us to skip parentheses completely for methods without parameters. To demonstrate this, let’s provide a method and a function with the same implementation:

def randomIntMethod(): Int = scala.util.Random.nextInt
val randomIntFunction = () => scala.util.Random.nextIntCopy

Whenever we write a method name, the compiler will consider it as a call to this method. In contrast, a function name without parentheses is just a variable that holds a reference to a function object:

scala> println(randomIntMethod)
1811098264
scala> println(randomIntFunction)
$line12.$read$$iw$$iw..$$iw$$iw$$$Lambda$4008/0x00000008015cac40@767ee25d
scala> println(randomIntFunction())
1292055875Copy

Thus, if we need to convert a method into a function, we can use the underscore:

val incFunction = inc _Copy

Variable incFunction contains a function, and we can use it as other functions we defined:

scala> println(incFunction(32))
33Copy

There is no corresponding way to convert a function into a method.

Nested Methods

We can define a method inside another method in Scala:

def outerMethod() = {
    def innerMethod() = {
        // inner method's statements here
    }

    // outer method's statements
    innerMethod()
}Copy

We can use the nesting feature in a situation when some method is tightly coupled with the context of another method. The code looks more organized when we use nesting.

Let’s see a common usage of a nested method via a recursive implementation of the factorial calculation:

import scala.annotation.tailrec

def factorial(num: Int): Int = {
    @tailrec
    def fact(num: Int, acc: Int): Int = {
        if (num == 0) 
            acc
        else
            fact(num - 1, acc * num)
    }

    fact(num, 1)
}Copy

The recursive implementation needs an extra method parameter to accumulate a temporal result between recursive calls, so we can use a decorative outer method with a simple interface to hide the implementation.

Parameterization

In Scala, we can parameterize a method by type. Parameterization allows us to create a generic method with the reusable code:

def pop[T](seq: Seq[T]): T = seq.headCopy

As we can see, the type parameter was provided in square brackets during the method declaration.

Now we’re able to use method pop for any type T we have and get the head of a sequence provided as a function parameter.

Simply put, we’ve generalized the pop function:

scala> val strings = Seq("a", "b", "c")
scala> val first = pop(strings)
first: String = aCopy
scala> val ints = Seq(10, 3, 11, 22, 10)
scala> val second = pop(ints)
second: Int = 10Copy

In this case, the compiler will infer the type, so corresponding values will be of the appropriate type. Value first will be of type String, and value second will be of type Int.

Extension Method

Scala’s implicit feature allows us to provide additional functionality to any existing type.

We just need to define a new type with methods and to provide an implicit conversion from the new type into an existing type:

implicit class IntExtension(val value: Int) extends AnyVal {
    def isOdd = value % 2 == 0
}Copy

Here, we defined a so-called value class. A value class is just a wrapper around any value of a particular type. In our example, the type of value is Int.

A value class is a class with exactly one parameter and only methods inside its body. We extend it from AnyVal type, and unnecessary object allocation will be avoided.

To define an implicit conversion, we have to mark a class as implicit. This allows all methods inside a class to become implicitly accessible.

When we import such a class into the current context, we can use all methods defined inside this class, as they are defined in a value type itself:

scala> 10.isOdd
res1: Boolean = true
scala> 11.isOdd
res2: Boolean = falseCopy

Int value 10 doesn’t have an explicit method isOdd, but IntExtension class does. The compiler will search for an implicit conversion from Int to IntExtension. Our value class provides such a conversion. The compiler will use it to resolve the call to the function isOdd with the necessary parameters.

By-Name Parameters

Until now, we’ve used “by-value” parameters. In other words, any parameter inside our function or method is evaluated before we can access that value:

def byValue(num: Int) = {
    println(s"First access: $num. Second access: $num")
}Copy
scala> byValue(scala.util.Random.nextInt)
First access: 1705836657. Second access: 1705836657Copy

We provided a function scala.util.Random.nextInt as a parameter to the call of our byValue function. As expected, the value of the parameter is evaluated before we use it in a println function. Both accesses to the parameter provide the same value.

Scala also supports “by-name” parameters. “By-name” parameter is evaluated every time we access them inside a function.

To define a “by-name” parameter, we have to prefix its type with => (arrow sign):

def byName(num: => Int) = {
    println(s"First access: $num. Second access: $num")
}Copy
scala> byName(scala.util.Random.nextInt)
First access: 1349966645. Second access: 236297674Copy

The call to the function is the same, but the result is slightly different. Each time we access the parameter provided “by-name”, we evaluate the parameter’s value again. That’s why each access got different results

Learn About The Basic Functions and Methods in Scala
Show Buttons
Hide Buttons