Scala function
last modified January 10, 2023
In this article we cover functions in Scala.
A function
A function is a mapping of zero or more input values to zero or more output values.
With functions, we can reduce code duplicity and increase its clarity. More complex tasks can be divided into simpler units using functions.
Functions can be assigned to variables, passed as arguments to functions or returned from other functions.
In Scala, there are two types of functions: val functions and def functions. There are some technical differences between these two types of functions. When needed, a def function is transformed into a val function with the underscore operator. The transformation is done often automatically so the distinction is somewhat blurred.
Scala main function
A main function is the entry point in a Scala application. It is decorated with
the @main
annotation.
@main def main() = println("main function is an entry point")
The name and the parameters of a main function are terminated with the
=
operator. The operator is followed with a block of body code. In
Scala 3, white space is a part of the syntax. It is used to delimit the body of
a function.
println("main function is an entry point")
The println
is a built-in function, which prints the text to the
terminal and adds a newline character.
$ scala main.scala main function is an entry point
A main function can take arguments.
@main def main(args: String*) = args.foreach(println)
In the program, the main function can take arbitrary number of string arguments.
$ scala main.scala an old falcon an old falcon
Returning values
Traditionally, the return
keywords is used to return a value from
a function. In Scala, the return
keyword is optional.
def square(x: Int): Int = // return x * x x * x @main def main() = println(square(5))
We define a square
function.
def square(x: Int): Int =
The def
keyword is followed by a name of the function. In square
brackets, we provide the function parameters. The brackets are followed by the
colon character and the data type of the returned value. After the
=
character, we provide the body of the function.
// return x * x x * x
In Scala, the return
keywords is optional. The value of the last
expression is automatically returned to the caller.
println(square(5))
We call the square
function with a 5 number as its parameter.
The computed value is printed to the console.
$ scala main.scala 25
Variable number of arguments
An asterisk is used to define variable number of arguments.
def mysum(vals: Int*): Int = var total = 0 for n <- vals do total += n total @main def main() = val s1 = mysum(1, 2, 3) val s2 = mysum(1, 2, 3, 4) val s3 = mysum(1, 2, 3, 4, 5) println(s1) println(s2) println(s3)
The program contains a definition of a sum function. The function can take any number of arguments. The arguments are available in the function body as a sequence of values.
def mysum(vals: Int*): Int = var total = 0 for n <- vals do total += n total
This is the definition of a custom mysum
function. The
vals
is a sequence of integer values. With a for loop, we compute
the final sum.
val s1 = mysum(1, 2, 3) val s2 = mysum(1, 2, 3, 4) val s3 = mysum(1, 2, 3, 4, 5)
We pass three, four, and five values to the function.
$ scala main.scala 6 10 15
Scala default function arguments
Scala function arguments may have default values; they are used if no value is provided for the argument.
def power(x: Int, n: Int = 2): Int = if n == 2 then x * x var res = 1 var i = 0 while i < n do res *= x i += 1 res @main def main() = val r1 = power(3) println(r1) val r2 = power(3, 3) println(r2)
We have a power
function. The function has one argument with an
implicit value. We can call the function with one or two arguments.
def power(x: Int, n: Int = 2): Int =
The second parameter of the power
function is implicit. If it is
not provided, its value is 2.
$ scala main.scala 9 27
Scala anonymous function
Anonymous functions do not have a name. In many cases, defining a named function is redundant.
@main def main() = val nums = List(1, 2, -3, -4, 5) val pos = nums.filter(e => e > 0).map(e => e * 2) println(pos) val neg = nums.filter(_ < 0) println(neg) nums.foreach(e => print(s"$e ")) println
We have a list of integers. We call some filter
, map
,
and foreach
functions on the list.
val pos = nums.filter(e => e > 0).map(e => e * 2)
The filter
and map
are functions that work on the
elements of the list. They take a predicate function (a function returning a
boolean value) as a parameter. In our case, the predicates are anonymous
functions.
val neg = nums.filter(_ < 0)
This is a shortened syntax.
$ scala main.scala List(2, 4, 10) List(-3, -4) 1 2 -3 -4 5
Scala val function
With val
keyword, we define function types.
val square = (x: Int) => x * x val triple: (x: Int) => Int = (x) => x * x * x @main def main() = val nums = List(1, 2, 3, 4, 5) val res = square(3) println(res) val res2 = triple(5) println(res2) val res3 = nums.map(square) println(res3) val square2 = square println(square2(5))
The program defines two val functions.
val square = (x: Int) => x * x
A function type is bound to the square
identifier. The parameters
of the functions are separated from its body with the fat arrow
=>
operator. The type declaration for the square
identifier is omitted, since Scala is able to infer it.
val triple: (x: Int) => Int = (x) => x * x * x
Here we omit the type for the function argument, but provide the type for the
tripe
identifier. (It is not possibe to omit both declaration.)
val res = square(3) println(res val res2 = triple(5) println(res2) val res3 = nums.map(square) println(res3)
The val functions are called exactly like def functions.
$ scala main.scala 9 125 List(1, 4, 9, 16, 25) 25
One difference between the val and def functions is that the def functions are always evaluated.
val getNano = System.nanoTime def getNano2 = System.nanoTime @main def main() = println(getNano) Thread.sleep(300) println(getNano) println("-----------------") println(getNano2) Thread.sleep(300) println(getNano2)
The getNano
and getNano2
functions return the
current time in nanoseconds.
val getNano = System.nanoTime
The getNano
function is evaluated once and subsequent calls return
the same value.
def getNano2 = System.nanoTime
The getNano2
function is always evaluated.
$ scala main.scala 32724329770177 32724329770177 ----------------- 32724630310958 32724930546360
Scala nested function
A nested function, also called an inner function, is a function defined inside another function.
def minmax(x: Int, y: Int) = val min = (x: Int, y: Int) => if x < y then x else y val max = (x: Int, y: Int) => if x > y then x else y var mn = min(x, y) var mx = max(x, y) (mn, mx) @main def main() = val res = minmax(100, 13) println(s"min: ${res._1} max: ${res._2}") val res2 = minmax(0, -13) println(s"min: ${res2._1} max: ${res2._2}")
In the program, we have the minmax
function, which contains two
nested functions: min
and max
.
$ scala main.scala min: 13 max: 100 min: -13 max: 0
Scala high-order function
High-order functions operate on other functions, either by taking them as arguments or by returning them.
def process(data: List[Int], f: (e: Int) => Int): List[Int] = data.map(f) @main def main() = val nums = List(1, 2, 3, 4, 5, 6) val res1 = process(nums, e => e * e) println(res1) val res2 = process(nums, e => e + 1) println(res2)
The process
is a high-order function.
def process(data: List[Int], f: (e: Int) => Int): List[Int] = data.map(f)
The process
high-order function applies the function on the list.
val res1 = process(nums, e => e * e) ... val res2 = process(nums, e => e + 1)
We pass two different anonymous functions to the process
function.
$ scala main.scala List(1, 4, 9, 16, 25, 36) List(2, 3, 4, 5, 6, 7)
Scala closure
A closure is an anonymous nested function which retains bindings to variables defined outside the body of the closure.
Closures can hold a unique state of their own. The state becomes isolated as we create new instances of the function.
def intSeq(): () => Int = var i = 0 return () => { i += 1; i } @main def main() = val nextInt = intSeq() println(nextInt()) println(nextInt()) println(nextInt()) println(nextInt()) println("-------------------") val nextInt2 = intSeq() println(nextInt2()) println(nextInt2())
We have the intSeq function, which generates a sequence of integers. It returns
a closure which increments the i
variable.
def intSeq(): () => Int = var i = 0 return () => { i += 1; i }
Variables defined in functions have a local function scope. However, in this
case, the closure is bound to the i
variable even after the
intSeq
function returns.
val nextInt = intSeq()
The intSeq
function is called. It returns a function which
increments a counter. The returned function closes over the variable
i
to form a closure. The closure is bound to the
nextInt
value.
println(nextInt()) println(nextInt()) println(nextInt()) println(nextInt())
We call the closure four times.
val nextInt2 = intSeq() println(nextInt2()) println(nextInt2())
The next call of the intSeq
function returns a new closure. This
new closure has its own distinct state.
$ scala main.scala 1 2 3 4 ------------------- 1 2
Scala extension function
Extension functions add functionality to an existing type. An extension
function is created with the extension
keyword.
extension (value: Int) def isOdd = value % 2 == 0 def isEven = value % 2 != 0 def times(f: Any => Unit, v: Any): Unit = var i = 0 while i < value do f(v) i += 1 @main def main() = val n = 4 println(n.isOdd) println(n.isEven) n.times(println, "falcon")
In the program, we add three extension function to the Int
type.
extension (value: Int) def isOdd = value % 2 == 0 def isEven = value % 2 != 0 def times(f: Any => Unit, v: Any): Unit = var i = 0 while i < value do f(v) i += 1
The isOdd
and isEven
functions check if the integer
is even or odd. The times
function executes the given function
n times.
$ scala main.scala true false falcon falcon falcon falcon
Scala member function
Member functions are functions defined within a Scala class.
class Cat: def talk() = println("meow") @main def main() = val missy = Cat() missy.talk()
The talk
function is defined in the Cat
class.
val missy = Cat() missy.talk()
We create the Cat
object and call the member function using the dot
operator.
$ scala main.scala meow
In this article we have worked with Scala functions.