Skip to content

Scala Methods and Functions

Methods and functions are core concepts in Scala programming. This chapter will detail method definitions, function literals, higher-order functions, and basic functional programming concepts.

Method Definitions

Basic Method Syntax

scala
// Basic method definition
def methodName(parameter1: Type1, parameter2: Type2): ReturnType = {
  // Method body
  returnValue
}

// Examples
def add(x: Int, y: Int): Int = {
  x + y
}

def greet(name: String): String = {
  s"Hello, $name!"
}

// Single-expression methods can omit braces
def multiply(x: Int, y: Int): Int = x * y

def square(x: Int): Int = x * x

No-Parameter Methods

scala
// No-parameter methods
def getCurrentTime(): Long = System.currentTimeMillis()

def pi(): Double = 3.14159

// Can omit parentheses (but call must also omit)
def randomNumber: Int = scala.util.Random.nextInt(100)

// Call methods
val time1 = getCurrentTime()  // With parentheses
val time2 = getCurrentTime    // Without parentheses (if defined with parentheses)
val number = randomNumber     // Must be without parentheses

Return Type Inference

scala
// Return type can be inferred
def add(x: Int, y: Int) = x + y  // Inferred as Int

def createList() = List(1, 2, 3)  // Inferred as List[Int]

// Recursive methods must explicitly declare return type
def factorial(n: Int): Int = {
  if (n <= 1) 1
  else n * factorial(n - 1)
}

// Complex methods should explicitly declare return type
def processData(data: List[String]): Map[String, Int] = {
  data.groupBy(identity).view.mapValues(_.length).toMap
}

Method Parameters

Default Parameters

scala
def greet(name: String, greeting: String = "Hello", punctuation: String = "!"): String = {
  s"$greeting, $name$punctuation"
}

// Call methods
val msg1 = greet("Alice")                    // "Hello, Alice!"
val msg2 = greet("Bob", "Hi")               // "Hi, Bob!"
val msg3 = greet("Charlie", "Hey", ".")     // "Hey, Charlie."

Named Parameters

scala
def createUser(name: String, age: Int, email: String, active: Boolean = true): String = {
  s"User($name, $age, $email, $active)"
}

// Use named parameters
val user1 = createUser(
  name = "Alice",
  age = 25,
  email = "alice@example.com"
)

val user2 = createUser(
  email = "bob@example.com",
  name = "Bob",
  age = 30,
  active = false
)

Variable Arguments

scala
def sum(numbers: Int*): Int = {
  numbers.sum
}

def concatenate(separator: String, strings: String*): String = {
  strings.mkString(separator)
}

// Call methods
val total1 = sum(1, 2, 3, 4, 5)
val total2 = sum()  // Empty argument list

val text1 = concatenate(", ", "apple", "banana", "cherry")
val text2 = concatenate(" - ")  // Only separator

// Pass collection as variable arguments
val numbers = List(1, 2, 3, 4, 5)
val total3 = sum(numbers: _*)  // Spread list

Call-by-Name Parameters

scala
// Call-by-value parameter (default)
def callByValue(x: Int): Int = {
  println("Evaluating call-by-value")
  x + x
}

// Call-by-name parameter
def callByName(x: => Int): Int = {
  println("Evaluating call-by-name")
  x + x
}

// Test difference
def expensiveComputation(): Int = {
  println("Computing...")
  Thread.sleep(1000)
  42
}

// Call-by-value: computed once, used twice
callByValue(expensiveComputation())

// Call-by-name: recomputed each time used
callByName(expensiveComputation())

Function Literals

Anonymous Functions

scala
// Basic anonymous function syntax
val add = (x: Int, y: Int) => x + y
val square = (x: Int) => x * x
val greet = (name: String) => s"Hello, $name!"

// Use anonymous functions
val result1 = add(3, 4)        // 7
val result2 = square(5)        // 25
val message = greet("Alice")   // "Hello, Alice!"

Function Types

scala
// Function type declarations
val multiply: (Int, Int) => Int = (x, y) => x * y
val isEven: Int => Boolean = x => x % 2 == 0
val printer: String => Unit = s => println(s)

// Complex function types
val processor: List[Int] => List[Int] = list => list.map(_ * 2)
val validator: String => Option[String] = s => 
  if (s.nonEmpty) Some(s) else None

Simplified Syntax

scala
val numbers = List(1, 2, 3, 4, 5)

// Full syntax
val doubled1 = numbers.map(x => x * 2)

// Simplified syntax (placeholders)
val doubled2 = numbers.map(_ * 2)
val filtered = numbers.filter(_ > 2)
val sum = numbers.reduce(_ + _)

// Multiple placeholders
val pairs = numbers.zip(numbers).map { case (x, y) => x + y }
val pairsSimple = numbers.zip(numbers).map(_ + _)  // Error: ambiguous

// Correct multi-parameter placeholders
val combined = List((1, 2), (3, 4)).map { case (a, b) => a + b }

Higher-Order Functions

Accepting Functions as Parameters

scala
def applyOperation(x: Int, y: Int, operation: (Int, Int) => Int): Int = {
  operation(x, y)
}

def applyToList[T, R](list: List[T], f: T => R): List[R] = {
  list.map(f)
}

// Use examples
val add = (x: Int, y: Int) => x + y
val multiply = (x: Int, y: Int) => x * y

val sum = applyOperation(3, 4, add)      // 7
val product = applyOperation(3, 4, multiply)  // 12

val numbers = List(1, 2, 3, 4, 5)
val squared = applyToList(numbers, (x: Int) => x * x)
val strings = applyToList(numbers, (x: Int) => s"Number: $x")

Methods that Return Functions

scala
def createMultiplier(factor: Int): Int => Int = {
  (x: Int) => x * factor
}

def createValidator(minLength: Int): String => Boolean = {
  (s: String) => s.length >= minLength
}

// Use examples
val double = createMultiplier(2)
val triple = createMultiplier(3)

val result1 = double(5)   // 10
val result2 = triple(4)   // 12

val isValidPassword = createValidator(8)
val isValidName = createValidator(2)

val valid1 = isValidPassword("secret123")  // true
val valid2 = isValidName("Al")            // true

Function Composition

scala
// Function composition
def compose[A, B, C](f: B => C, g: A => B): A => C = {
  (x: A) => f(g(x))
}

def andThen[A, B, C](f: A => B, g: B => C): A => C = {
  (x: A) => g(f(x))
}

// Example functions
val addOne = (x: Int) => x + 1
val double = (x: Int) => x * 2
val toString = (x: Int) => x.toString

// Compose functions
val addOneThenDouble = compose(double, addOne)  // Add 1 first, then multiply by 2
val doubleAndAddOne = andThen(double, addOne)   // Multiply by 2 first, then add 1

val result1 = addOneThenDouble(3)  // (3 + 1) * 2 = 8
val result2 = doubleAndAddOne(3)   // (3 * 2) + 1 = 7

// Chain composition
val pipeline = andThen(andThen(addOne, double), toString)
val result3 = pipeline(3)  // "8"

Currying

Curried Function Definitions

scala
// Regular multi-parameter function
def add(x: Int, y: Int): Int = x + y

// Curried function
def addCurried(x: Int)(y: Int): Int = x + y

// Manual currying
def addManual(x: Int): Int => Int = (y: Int) => x + y

// Use examples
val result1 = add(3, 4)           // 7
val result2 = addCurried(3)(4)    // 7
val result3 = addManual(3)(4)     // 7

// Partial application
val add5 = addCurried(5) _        // Int => Int
val result4 = add5(3)             // 8

val add10 = addManual(10)         // Int => Int
val result5 = add10(7)            // 17

Practical Currying Applications

scala
// Configuration functions
def createLogger(level: String)(component: String)(message: String): Unit = {
  println(s"[$level] $component: $message")
}

// Create specific level loggers
val infoLogger = createLogger("INFO") _
val errorLogger = createLogger("ERROR") _

// Create specific component loggers
val dbInfoLogger = infoLogger("Database")
val apiErrorLogger = errorLogger("API")

// Use
dbInfoLogger("Connection established")
apiErrorLogger("Request failed")

// Data processing pipeline
def processData(validator: String => Boolean)
               (transformer: String => String)
               (data: List[String]): List[String] = {
  data.filter(validator).map(transformer)
}

val isNotEmpty = (s: String) => s.nonEmpty
val toUpperCase = (s: String) => s.toUpperCase

val processor = processData(isNotEmpty)(toUpperCase) _
val result = processor(List("hello", "", "world", "scala"))
// List("HELLO", "WORLD", "SCALA")

Partial Application Functions

Basic Partial Application

scala
def multiply(x: Int, y: Int, z: Int): Int = x * y * z

// Partial application
val multiplyBy2 = multiply(2, _, _)      // (Int, Int) => Int
val multiplyBy2And3 = multiply(2, 3, _)  // Int => Int

val result1 = multiplyBy2(3, 4)          // 24
val result2 = multiplyBy2And3(5)         // 30

// Use underscore for partial application
val numbers = List(1, 2, 3, 4, 5)
val doubled = numbers.map(multiply(2, _, 1))  // List(2, 4, 6, 8, 10)

Complex Partial Application

scala
def createConnection(host: String, port: Int, timeout: Int, ssl: Boolean): String = {
  s"Connection to $host:$port (timeout: ${timeout}s, ssl: $ssl)"
}

// Create specific environment connection functions
val prodConnection = createConnection("prod.example.com", 443, _, true)
val devConnection = createConnection("localhost", 8080, _, false)

val prod = prodConnection(30)  // Production, 30s timeout
val dev = devConnection(5)     // Development, 5s timeout

Recursive Functions

Tail Recursion Optimization

scala
import scala.annotation.tailrec

// Non-tail recursive (may cause stack overflow)
def factorial(n: Int): Int = {
  if (n <= 1) 1
  else n * factorial(n - 1)
}

// Tail recursive version
def factorialTailRec(n: Int): Int = {
  @tailrec
  def loop(n: Int, acc: Int): Int = {
    if (n <= 1) acc
    else loop(n - 1, n * acc)
  }
  loop(n, 1)
}

// Fibonacci sequence
def fibonacci(n: Int): Int = {
  @tailrec
  def fib(n: Int, a: Int, b: Int): Int = {
    if (n == 0) a
    else fib(n - 1, b, a + b)
  }
  fib(n, 0, 1)
}

Mutual Recursion

scala
def isEven(n: Int): Boolean = {
  if (n == 0) true
  else isOdd(n - 1)
}

def isOdd(n: Int): Boolean = {
  if (n == 0) false
  else isEven(n - 1)
}

// Use examples
val even = isEven(4)  // true
val odd = isOdd(5)    // true

Functional Programming Concepts

Pure Functions

scala
// Pure function: same input always produces same output, no side effects
def add(x: Int, y: Int): Int = x + y
def multiply(x: Int, y: Int): Int = x * y

// Impure function: has side effects
var counter = 0
def impureIncrement(): Int = {
  counter += 1  // Side effect: modify external state
  counter
}

def impureRandom(): Int = {
  scala.util.Random.nextInt(100)  // Side effect: non-determinism
}

// Pure function versions
def pureIncrement(current: Int): Int = current + 1
def pureRandom(seed: Long): (Long, Int) = {
  val rng = new scala.util.Random(seed)
  (seed + 1, rng.nextInt(100))
}

Immutability

scala
// Immutable data structure operations
val numbers = List(1, 2, 3, 4, 5)

// All operations return new collections
val doubled = numbers.map(_ * 2)
val filtered = numbers.filter(_ > 2)
val sorted = numbers.sorted
val reversed = numbers.reverse

// Original list remains unchanged
println(numbers)  // List(1, 2, 3, 4, 5)

// Functional update
case class Person(name: String, age: Int)

def updateAge(person: Person, newAge: Int): Person = {
  person.copy(age = newAge)
}

val alice = Person("Alice", 25)
val olderAlice = updateAge(alice, 26)
// alice remains unchanged, olderAlice is a new instance

Practical Application Examples

Data Processing Pipeline

scala
case class Employee(name: String, department: String, salary: Double, years: Int)

val employees = List(
  Employee("Alice", "Engineering", 75000, 3),
  Employee("Bob", "Sales", 65000, 5),
  Employee("Charlie", "Engineering", 85000, 7),
  Employee("Diana", "Marketing", 60000, 2),
  Employee("Eve", "Engineering", 95000, 10)
)

// Functional data processing
def processEmployees(employees: List[Employee]): Map[String, Double] = {
  employees
    .filter(_.years >= 3)                    // Filter experienced employees
    .groupBy(_.department)                   // Group by department
    .view.mapValues(_.map(_.salary).sum)     // Calculate total salary per department
    .toMap
}

val departmentSalaries = processEmployees(employees)

// Use higher-order functions to create flexible processors
def createEmployeeProcessor(
  filter: Employee => Boolean,
  groupBy: Employee => String,
  aggregate: List[Employee] => Double
): List[Employee] => Map[String, Double] = {
  employees =>
    employees
      .filter(filter)
      .groupBy(groupBy)
      .view.mapValues(aggregate)
      .toMap
}

// Create specific processors
val seniorEmployeeAvgSalary = createEmployeeProcessor(
  _.years >= 5,                              // Filter condition
  _.department,                              // Group condition
  emps => emps.map(_.salary).sum / emps.length  // Aggregate function
)

Function Composition Examples

scala
// String processing functions
val trim = (s: String) => s.trim
val toLowerCase = (s: String) => s.toLowerCase
val removeSpaces = (s: String) => s.replaceAll("\\s+", "")
val capitalize = (s: String) => s.capitalize

// Compose functions
def pipe[A, B, C, D](f1: A => B, f2: B => C, f3: C => D): A => D = {
  a => f3(f2(f1(a)))
}

val normalizeString = pipe(trim, toLowerCase, removeSpaces)
val formatTitle = pipe(trim, toLowerCase, capitalize)

// Use examples
val input = "  Hello World  "
val normalized = normalizeString(input)  // "helloworld"
val title = formatTitle(input)           // "Hello world"

// More complex pipeline
val processText: String => List[String] = { text =>
  text
    .split("\\.")
    .map(trim)
    .filter(_.nonEmpty)
    .map(capitalize)
    .toList
}

val sentences = processText("hello world. this is scala. functional programming is great.")
// List("Hello world", "This is scala", "Functional programming is great")

Exercises

Exercise 1: Higher-Order Function Implementation

scala
object HigherOrderFunctionExercise {
  // Implement map function
  def myMap[A, B](list: List[A], f: A => B): List[B] = {
    list match {
      case Nil => Nil
      case head :: tail => f(head) :: myMap(tail, f)
    }
  }
  
  // Implement filter function
  def myFilter[A](list: List[A], predicate: A => Boolean): List[A] = {
    list match {
      case Nil => Nil
      case head :: tail =>
        if (predicate(head)) head :: myFilter(tail, predicate)
        else myFilter(tail, predicate)
    }
  }
  
  // Implement reduce function
  def myReduce[A](list: List[A], f: (A, A) => A): A = {
    list match {
      case Nil => throw new IllegalArgumentException("Empty list")
      case head :: Nil => head
      case head :: tail => f(head, myReduce(tail, f))
    }
  }
  
  def main(args: Array[String]): Unit = {
    val numbers = List(1, 2, 3, 4, 5)
    
    val doubled = myMap(numbers, (x: Int) => x * 2)
    val evens = myFilter(numbers, (x: Int) => x % 2 == 0)
    val sum = myReduce(numbers, (x: Int, y: Int) => x + y)
    
    println(s"Original: $numbers")
    println(s"Doubled: $doubled")
    println(s"Evens: $evens")
    println(s"Sum: $sum")
  }
}

Exercise 2: Functional Calculator

scala
object FunctionalCalculator {
  type Operation = (Double, Double) => Double
  
  val add: Operation = _ + _
  val subtract: Operation = _ - _
  val multiply: Operation = _ * _
  val divide: Operation = (x, y) => if (y != 0) x / y else throw new ArithmeticException("Division by zero")
  
  def calculate(x: Double, y: Double, op: Operation): Double = op(x, y)
  
  def createCalculator(op: Operation): (Double, Double) => Double = op
  
  // Chain calculations
  def chain(initial: Double, operations: List[(Operation, Double)]): Double = {
    operations.foldLeft(initial) { case (acc, (op, value)) =>
      op(acc, value)
    }
  }
  
  def main(args: Array[String]): Unit = {
    // Basic calculations
    println(calculate(10, 5, add))      // 15.0
    println(calculate(10, 5, multiply)) // 50.0
    
    // Create specialized calculators
    val adder = createCalculator(add)
    val multiplier = createCalculator(multiply)
    
    println(adder(3, 4))      // 7.0
    println(multiplier(3, 4)) // 12.0
    
    // Chain calculations: (10 + 5) * 2 - 3 / 3
    val result = chain(10, List(
      (add, 5),
      (multiply, 2),
      (subtract, 3),
      (divide, 3)
    ))
    println(s"Chain result: $result") // 27.0
  }
}

Summary

This chapter detailed core concepts of methods and functions in Scala:

  • Method definitions: Basic syntax, parameter handling, return types
  • Function literals: Anonymous functions, function types, simplified syntax
  • Higher-order functions: Methods that accept and return functions
  • Currying: Step-by-step application of function parameters
  • Recursion: Tail recursion optimization and mutual recursion
  • Functional programming: Pure functions, immutability, function composition

Mastering these concepts is the foundation for functional programming and the key to understanding Scala's powerful expressiveness. In the next chapter, we will learn Scala Closures, diving deeper into function scope and variable capture mechanisms.

Content is for learning and research only.