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.

Leave a Comment

Your email address will not be published. Required fields are marked *