Scala Currying
Currying transforms a function that takes multiple arguments into a chain of functions, each taking one argument. Named after mathematician Haskell Curry, this technique lets you partially apply a function — supply some arguments now and the rest later. The result is a more specialized function ready for reuse.
The Core Idea
Without currying: With currying:
───────────────── ─────────────────────────────
add(3, 5) = 8 add(3) returns a function
add(3)(5) = 8
│
You can save add(3) and reuse it!
Regular vs Curried Function
// Regular function — takes both arguments at once
def add(a: Int, b: Int): Int = a + b
add(3, 5) // 8
// Curried function — takes one argument, returns a function
def addCurried(a: Int)(b: Int): Int = a + b
addCurried(3)(5) // 8
// Partial application — supply only the first argument
val addThree = addCurried(3) // returns a function Int => Int
println(addThree(5)) // 8
println(addThree(10)) // 13
println(addThree(100)) // 103
Think of currying like a coffee machine with two slots. You first choose the coffee type (first argument). You now have a half-configured machine. Later, you choose the cup size (second argument). The result: your coffee. The first choice produces a machine ready for the second choice.
Multiple Parameter Lists
Scala achieves currying through multiple parameter lists — groups of parameters separated by individual parentheses sets:
def multiply(x: Int)(y: Int)(z: Int): Int = x * y * z
multiply(2)(3)(4) // 24
val double = multiply(2) // Int => Int => Int
val sixTimes = multiply(2)(3) // Int => Int
println(double(5)(1)) // 10
println(sixTimes(7)) // 42
Practical Use: Custom Iterators
Currying shines when you want to create specialized versions of general functions:
def applyDiscount(discountPercent: Double)(price: Double): Double =
price * (1 - discountPercent / 100)
val tenPercentOff = applyDiscount(10)
val halfPrice = applyDiscount(50)
val freeItem = applyDiscount(100)
println(tenPercentOff(200)) // 180.0
println(halfPrice(500)) // 250.0
println(freeItem(300)) // 0.0
val cart = List(199.0, 450.0, 89.0, 670.0)
val discountedCart = cart.map(tenPercentOff)
println(discountedCart) // List(179.1, 405.0, 80.1, 603.0)
Currying with Functions That Take Functions
Currying works especially well with higher-order functions. The classic example: map, filter, and foldLeft all use currying internally in Scala.
// foldLeft has two parameter lists
List(1, 2, 3, 4, 5).foldLeft(0)(_ + _) // 15
List(1, 2, 3, 4, 5).foldLeft(1)(_ * _) // 120
// You can save the partial application
val sumList = List(1, 2, 3, 4, 5).foldLeft(0) _
// sumList is now a function (Int, Int) => Int => Int waiting for the combining function
Building a Logger with Currying
def log(level: String)(component: String)(message: String): Unit =
println(s"[$level] [$component] $message")
// Create specialized loggers
val infoLog = log("INFO")
val errorLog = log("ERROR")
val warnLog = log("WARN")
// Use in a specific component
val dbLog = infoLog("Database")
val apiLog = infoLog("API")
dbLog("Connection established") // [INFO] [Database] Connection established
apiLog("Request received: /users") // [INFO] [API] Request received: /users
errorLog("Auth")("Token expired") // [ERROR] [Auth] Token expired
log("INFO") ───────→ component logger (String => String => Unit)
│
log("INFO")("Database") ─────→ message logger (String => Unit)
│
log("INFO")("Database")("msg") ───────→ Unit (prints!)
Converting Regular Functions to Curried with .curried
Scala can automatically curry a regular function using the .curried method:
def power(base: Int, exp: Int): Int = Math.pow(base, exp).toInt
val curriedPower = (power _).curried // Int => Int => Int
val square = curriedPower(2) // wait — this means base=2, so 2^exp
val cubed = curriedPower(3) // 3^exp
println(square(10)) // 2^10 = 1024
println(cubed(4)) // 3^4 = 81
Converting Curried Functions Back with Function.uncurried
def addCurried(a: Int)(b: Int): Int = a + b
val addNormal = Function.uncurried(addCurried _)
// addNormal is now (Int, Int) => Int
println(addNormal(3, 5)) // 8
Using Currying with Context Parameters (Implicit Arguments)
Currying separates "configuration" parameters from "data" parameters. Context parameters (Scala 3's replacement for implicits) follow the same principle — they come in a separate parameter list:
def greetFormal(greeting: String)(name: String): String =
s"$greeting, $name."
def greetWithTitle(title: String)(name: String): String =
s"Dear $title $name,"
val hello = greetFormal("Hello")
val goodMorning = greetFormal("Good morning")
val mrGreet = greetWithTitle("Mr.")
val drGreet = greetWithTitle("Dr.")
println(hello("Alice")) // Hello, Alice.
println(goodMorning("Ravi")) // Good morning, Ravi.
println(mrGreet("Smith")) // Dear Mr. Smith,
println(drGreet("Patel")) // Dear Dr. Patel,
val employees = List("Wang", "Silva", "Okonkwo")
employees.map(mrGreet).foreach(println)
// Dear Mr. Wang,
// Dear Mr. Silva,
// Dear Mr. Okonkwo,
Real-World Example: Validation Pipeline
def validate(minLength: Int)(maxLength: Int)(input: String): Option[String] =
if input.length >= minLength && input.length <= maxLength
then Some(input)
else None
// Create specialized validators
val validateUsername = validate(3)(20)
val validatePassword = validate(8)(64)
val validateShortCode = validate(4)(4)
println(validateUsername("alice")) // Some(alice)
println(validateUsername("ab")) // None (too short)
println(validatePassword("secret123")) // Some(secret123)
println(validatePassword("hi")) // None (too short)
println(validateShortCode("AB12")) // Some(AB12)
println(validateShortCode("ABC")) // None (not exactly 4)
When to Use Currying
Good use cases:
✓ Creating specialized versions of a general function
✓ Separating configuration from data parameters
✓ Building readable DSLs (domain-specific languages)
✓ Working with higher-order functions that expect curried form
✓ Dependency injection without a framework
Avoid when:
✗ Simple two-argument arithmetic (overkill)
✗ When all arguments are always supplied together
✗ When readability suffers from the chained () () ()
Currying is not just a theoretical concept — it appears throughout Scala's standard library and production codebases. Understanding it unlocks a new level of code reuse and composability.
