Scala - Function Parameters (Default, Named, Variable Args)
• Scala's default parameters eliminate method overloading boilerplate by allowing you to specify fallback values directly in the parameter list, reducing code duplication by up to 70% compared to...
Key Insights
• Scala’s default parameters eliminate method overloading boilerplate by allowing you to specify fallback values directly in the parameter list, reducing code duplication by up to 70% compared to Java-style overloading.
• Named arguments enable calling functions with parameters in any order, making APIs more readable and maintainable—especially critical when dealing with functions that have multiple parameters of the same type.
• Variable-length arguments (varargs) in Scala compile to sequences and support both passing individual elements and expanding existing collections using the : _* syntax, providing flexibility without sacrificing type safety.
Default Parameters
Default parameters allow you to specify fallback values for function parameters. When calling the function, you can omit arguments that have defaults, and Scala will use the specified values.
def createUser(name: String, age: Int = 18, isActive: Boolean = true): String = {
s"User: $name, Age: $age, Active: $isActive"
}
// Call with all arguments
println(createUser("Alice", 25, false))
// Output: User: Alice, Age: 25, Active: false
// Call with only required arguments
println(createUser("Bob"))
// Output: User: Bob, Age: 18, Active: true
// Call with some defaults overridden
println(createUser("Charlie", 30))
// Output: User: Charlie, Age: 30, Active: true
Default parameters are evaluated at call time, not at definition time. This means you can use expressions, including calls to other functions:
def getCurrentTimestamp(): Long = System.currentTimeMillis()
def logMessage(message: String, timestamp: Long = getCurrentTimestamp()): Unit = {
println(s"[$timestamp] $message")
}
logMessage("First message")
Thread.sleep(1000)
logMessage("Second message")
// Each call gets a different timestamp
Default parameters work with case classes, making them particularly useful for configuration objects:
case class DatabaseConfig(
host: String = "localhost",
port: Int = 5432,
maxConnections: Int = 10,
timeout: Int = 30000
)
val defaultConfig = DatabaseConfig()
val customConfig = DatabaseConfig(host = "prod-db.example.com", maxConnections = 50)
Named Arguments
Named arguments let you specify which parameter you’re providing a value for by using the parameter name. This feature works seamlessly with default parameters and improves code clarity.
def sendEmail(
to: String,
subject: String,
body: String,
cc: String = "",
bcc: String = "",
priority: String = "normal"
): Unit = {
println(s"Sending email to $to with subject '$subject'")
if (cc.nonEmpty) println(s"CC: $cc")
if (bcc.nonEmpty) println(s"BCC: $bcc")
println(s"Priority: $priority")
}
// Using named arguments to skip middle parameters
sendEmail(
to = "user@example.com",
subject = "Important Update",
body = "Please review the attached document",
priority = "high"
)
// Named arguments can appear in any order
sendEmail(
body = "Meeting at 3 PM",
subject = "Team Meeting",
to = "team@example.com"
)
Named arguments are especially valuable when dealing with boolean flags or multiple parameters of the same type:
def processData(
input: String,
validateInput: Boolean,
transformData: Boolean,
saveResults: Boolean
): Unit = {
println(s"Processing $input")
println(s"Validate: $validateInput, Transform: $transformData, Save: $saveResults")
}
// Without named arguments - unclear what each boolean means
processData("data.csv", true, false, true)
// With named arguments - self-documenting
processData(
input = "data.csv",
validateInput = true,
transformData = false,
saveResults = true
)
You can mix positional and named arguments, but all positional arguments must come before named ones:
def createReport(title: String, author: String, pages: Int = 10, draft: Boolean = true): String = {
s"Report: $title by $author ($pages pages, draft: $draft)"
}
// Valid: positional then named
println(createReport("Q4 Results", "John", draft = false))
// Invalid: would cause compilation error
// println(createReport(title = "Q4 Results", "John"))
Variable-Length Arguments
Variable-length arguments (varargs) allow functions to accept an arbitrary number of arguments of the same type. In Scala, varargs are declared using an asterisk after the type.
def sum(numbers: Int*): Int = {
numbers.sum
}
println(sum(1, 2, 3)) // Output: 6
println(sum(1, 2, 3, 4, 5)) // Output: 15
println(sum()) // Output: 0
def concatenate(separator: String, strings: String*): String = {
strings.mkString(separator)
}
println(concatenate(", ", "apple", "banana", "orange"))
// Output: apple, banana, orange
Varargs parameters are compiled to Seq types, so you can use all sequence operations:
def findMax(numbers: Int*): Option[Int] = {
if (numbers.isEmpty) None
else Some(numbers.max)
}
println(findMax(3, 7, 2, 9, 1)) // Output: Some(9)
println(findMax()) // Output: None
def printStats(values: Double*): Unit = {
if (values.nonEmpty) {
println(s"Count: ${values.size}")
println(s"Sum: ${values.sum}")
println(s"Average: ${values.sum / values.size}")
}
}
printStats(10.5, 20.3, 15.7, 8.9)
To pass an existing collection to a varargs parameter, use the : _* syntax:
def multiply(numbers: Int*): Int = {
numbers.product
}
val numberList = List(2, 3, 4)
val numberArray = Array(2, 3, 4)
println(multiply(numberList: _*)) // Output: 24
println(multiply(numberArray: _*)) // Output: 24
// This won't compile - can't pass collection directly
// println(multiply(numberList))
A function can have at most one varargs parameter, and it must be the last parameter:
def logWithTags(level: String, message: String, tags: String*): Unit = {
val tagString = if (tags.nonEmpty) tags.mkString("[", ", ", "]") else ""
println(s"[$level] $message $tagString")
}
logWithTags("INFO", "Application started", "startup", "init")
// Output: [INFO] Application started [startup, init]
logWithTags("ERROR", "Connection failed")
// Output: [ERROR] Connection failed
Combining All Three Features
These features work together to create flexible, readable APIs:
case class HttpRequest(
method: String = "GET",
url: String,
headers: Map[String, String] = Map.empty,
timeout: Int = 5000
)
def makeRequest(request: HttpRequest, retries: Int = 3, retryDelays: Int*): String = {
val delays = if (retryDelays.isEmpty) Seq(1000, 2000, 4000) else retryDelays
println(s"Making ${request.method} request to ${request.url}")
println(s"Timeout: ${request.timeout}ms, Retries: $retries")
println(s"Retry delays: ${delays.mkString(", ")}ms")
"Response data"
}
// Using all features together
makeRequest(
request = HttpRequest(
method = "POST",
url = "https://api.example.com/data",
headers = Map("Authorization" -> "Bearer token123")
),
retries = 5,
retryDelays = 500, 1000, 2000, 4000, 8000
)
// Minimal usage with defaults
makeRequest(HttpRequest(url = "https://api.example.com/status"))
// Custom retry delays from a list
val customDelays = List(100, 200, 400)
makeRequest(
request = HttpRequest(url = "https://api.example.com/health"),
retryDelays = customDelays: _*
)
This combination enables builder-pattern-like flexibility without the boilerplate, making Scala APIs both powerful and ergonomic. The key is understanding when each feature provides the most value: default parameters for sensible fallbacks, named arguments for clarity, and varargs for collections of similar items.