Multiple Parameter Lists
Learn to define functions with multiple parameter lists in Scala for Python developers. Master currying and partial application in this hands-on tutorial.
Focus: define functions with multiple parameter lists
If you've come from Python, you're used to defining functions with a single parameter list: def greet(name, greeting): .... But Scala gives you a superpower that Python lacks — multiple parameter lists — and once you master it, you'll unlock a whole new level of functional expressiveness. In this lesson, you'll learn how to define functions with multiple parameter lists, why Scala does this, and how it enables currying, partial application, and clean, readable APIs. This is a step that will change how you think about function design — let's dive in.
The problem this lesson solves
When you're building real-world applications, you often want to reuse a function with the same fixed configuration across many calls. In Python, you might do this:
# Python: manually fixing arguments
def connect(host, port, timeout):
return f"Connecting to {host}:{port} with timeout {timeout}"
# Reuse with the same host and port
connect("localhost", 8080, 30)
connect("localhost", 8080, 60)
That works, but it repeats arguments and forces you to keep them in the right order every time. The pain becomes worse when you have many parameters — you'll find yourself passing the same configuration over and over. In Scala, multiple parameter lists let you bake in the common parameters and then call the remaining ones freely. This isn't just syntax sugar — it enables clean partial application and smoother composition, two concepts you'll use in functional programming all the time.
Core concept / mental model
Think of a function with multiple parameter lists as a vending machine with separate slots that accept coins one at a time. You insert the first coin, then the second, and so on — you can stop at any point and carry the machine with you. Each parameter list is a separate currying step: you give the function one group of arguments, and it returns a new function that expects the next group.
In Scala, a function with two parameter lists looks like this:
def greet(greeting: String)(name: String): String = s"$greeting, $name!"
When you call greet("Hello")("Alice"), you provide both groups. But you can also say val helloGreeter = greet("Hello") — that gives you a function expecting just the name. This is partial application in action.
Key idea: Every parameter list creates a new function. The last parameter list is what actually returns the final result, but intermediate lists return functions that "remember" the earlier arguments.
How it works step by step
Step 1: Define the function with multiple parameter lists
In Scala, you simply write multiple parentheses ( ) groups in your def declaration. Each group can have one or more parameters. Example:
def add(a: Int)(b: Int): Int = a + b
This defines a function add that takes two parameter lists: the first with a, the second with b. You call it as add(2)(3) — the result is 5.
Step 2: Understand what the compiler sees
For a method with two parameter lists, the Scala compiler treats it as a curried function. Under the hood, def add(a: Int)(b: Int): Int is roughly equivalent to a function that takes a and returns a function that takes b. You can see this by using the underscore _ to create a partially applied function:
val add2 = add(2) _ // underscore lifts it to a function
println(add2(3)) // prints 5
Step 3: Turn it into a function value with _
The underscore _ is crucial when you want to convert a method into a function value — this is called lifting. Without the underscore, Scala might complain that you're missing the second parameter list. Why is that useful? Because it lets you pass the partially applied function to other higher-order functions like map or foreach.
Hands-on walkthrough
Let's make it concrete. Suppose you're building a simple logging system. You want to log messages at different levels but always include a timestamp. With multiple parameter lists, you can fix the formatting first, then the level, then the message.
def log(formatter: String => String)(level: String)(message: String): Unit = {
val formatted = formatter(message)
println(s"[$level] $formatted")
}
// A simple formatter that uppercases
val upperFormatter: String => String = _.toUpperCase
val infoLogger = log(upperFormatter)("INFO") _ // fix formatter and level
infoLogger("User logged in") // prints [INFO] USER LOGGED IN
infoLogger("Database connected") // prints [INFO] DATABASE CONNECTED
Expected output:
[INFO] USER LOGGED IN
[INFO] DATABASE CONNECTED
Now let's compare with Python. In Python, you can achieve similar behavior with functools.partial, but it's not a first-class language feature:
from functools import partial
def log(formatter, level, message):
print(f"[{level}] {formatter(message)}")
upper = lambda s: s.upper()
info_logger = partial(log, upper, "INFO")
info_logger("User logged in")
info_logger("Database connected")
The Python version works, but Scala's multiple parameter lists make the partial application part clearer and more idiomatic — you don't need an extra import or helper function.
A practical exercise: curried mathematical operations
Let's build a small calculator that uses multiple parameter lists to create custom operations.
def operate(op: String)(a: Double)(b: Double): Double = op match {
case "+" => a + b
case "-" => a - b
case "*" => a * b
case "/" => if (b != 0) a / b else throw new IllegalArgumentException("Division by zero")
case _ => throw new IllegalArgumentException(s"Unknown operator: $op")
}
val add = operate("+") _ // (a: Double) => (b: Double) => a + b
val add10 = add(10) _ // (b: Double) => 10 + b
println(add10(5)) // 15.0
println(operate("*")(3)(4)) // 12.0
Here, add10 is a function that adds 10 to any number. This pattern is incredibly handy when you want to reuse a customized version of a function throughout your codebase.
Compare options / when to choose what
In Scala, you have several ways to define a function with multiple parameters. Here's a comparison table to help you choose:
| Approach | Example | Use case | Pros | Cons |
|---|---|---|---|---|
| Multiple parameter lists | def f(a: Int)(b: Int) = a + b |
Currying, partial application, implicit parameter lists | Natural currying, allows implicit, enables partial application with _ |
More syntax, may confuse newbies |
| Single parameter list | def f(a: Int, b: Int) = a + b |
Simple functions where no partial application needed | Straightforward, familiar from Python | No easy currying, must repeat all args |
| Returning a function | def f(a: Int): Int => Int = b => a + b |
When you want to be explicit about returning a function | Explicit, shows the function type | More verbose, less idiomatic |
Using curried method |
def f(a: Int)(b: Int) = a + b; val g = f.curried |
When you have an existing method and need a curried version | Can convert later | Requires extra step |
When to choose what? Use multiple parameter lists when you want to:
- Enable partial application for configuration reuse.
- Define implicit parameter lists (covered in a later lesson).
- Improve readability when you have logical groups of parameters.
Stick to a single parameter list when your function is simple and rarely used in a partially applied way.
Troubleshooting & edge cases
missing argument list error
If you call a function with multiple parameter lists but only provide the first list, Scala will raise a compile-time error: "missing argument list for method ..." This may surprise you if you expected partial application without the underscore. Example:
def greet(greeting: String)(name: String) = s"$greeting $name"
val g = greet("Hello") // error: missing argument list
Fix: Add an underscore to lift it into a function: val g = greet("Hello") _.
Forgetting the underscore when assigning to a function variable
If you write val f = someMethod _ but the method has multiple parameter lists, you need the underscore after all parameter lists to get a function value. For example:
def add(a: Int)(b: Int) = a + b
val addCurried = add _ // works: (Int) => (Int) => Int
val addPartial = add(1) _ // works: (Int) => Int
If you miss the underscore, you'll get an error.
Overloading with multiple parameter lists can confuse
If you have overloaded methods with the same name but different parameter lists, Scala may have trouble resolving the right one. This can lead to ambiguity errors. Keep overloads simple, or avoid them when using curried style.
Edge case: Empty parameter lists
You can have an empty parameter list: def f()(a: Int) = a. Calling f()(5) works, but f(5) will not. This can be a source of subtle bugs, so avoid empty lists unless you have a good reason.
What you learned & what's next
You now understand how to define functions with multiple parameter lists in Scala: you saw the mental model of currying, learned to use partial application with the underscore, and compared it to Python's functools.partial. You also practiced a real-world logging example and avoided common pitfalls like the missing argument list error.
Key takeaways from this lesson:
- Multiple parameter lists allow step-by-step argument application.
- The underscore
_converts a partially applied method into a function value. - Currying enables clean configuration reuse and function composition.
What's next? In the next lesson, you'll explore partial application and currying in more depth, including how to use these techniques with higher-order functions like map and flatMap. Get ready to take your functional programming skills to the next level!
Now, continue to the next lesson in this track to deepen your understanding of currying and partial application.
Practice recap
Try defining a configure function with three parameter lists (e.g., connection config, request params, and callbacks). Use partial application to create a 'production-ready' client that only takes request params. Then try lifting it with _ and passing it to map to transform a list of requests.
Common mistakes
- Forgetting the underscore when assigning a partially applied function —
val f = greet("Hello")errors; add_to lift it to a function. - Confusing a multiple parameter list function with a single parameter list — ensuring you call
add(1)(2)and notadd(1, 2). - Using empty parameter lists unintentionally, causing calls like
f()(5)instead off(5)— avoid empty lists unless needed. - Overloading curried methods can cause ambiguity — keep overloaded names simple or use distinct names.
Variations
- Using the
curriedmethod on an existing method to convert a single parameter list into a curried function. - Using
functools.partialin Python for comparison — but it's not a language feature. - Defining a function that returns a lambda:
def f(a: Int) = (b: Int) => a + bfor explicit function types.
Real-world use cases
- Building a database query API where you fix the connection config first, then the query string — use currying for clean config reuse.
- Creating a logging utility with a fixed formatter and level, then passing the message each time — avoids repeating config.
- Designing a mathematical library where you pre-define operators (add, multiply) and then apply them to numbers in data pipelines.
Key takeaways
- Multiple parameter lists enable currying and partial application in Scala.
- Use the underscore
_to lift a method into a function value. - Partial application lets you fix configuration arguments and reuse the function easily.
- Compare with Python's
functools.partialto understand the difference. - Be careful with missing argument list errors and overloaded curried methods.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.