Groovy Curry and Partial Application – Complete Guide with 10 Examples

Learn Groovy curry, rcurry, ncurry, and partial application with 10+ tested examples. Build reusable closures, loggers, validators, and pipelines on Groovy 5.x.

“Currying is the art of asking for less — turning a function that needs three ingredients into one that already has two.”

Haskell Curry, Mathematician

Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Intermediate to Advanced | Reading Time: 16 minutes

Groovy curry lets you take a closure with multiple parameters and pre-fill some of them, producing a new closure that requires fewer arguments. If you already know about closures, delegate and owner, and higher-order functions like collect and inject, currying is the natural next step for building reusable, configurable closures.

Groovy curry lets you take a closure with multiple parameters and “pre-fill” some of them, producing a new closure that requires fewer arguments. This technique — formally called groovy partial application — is incredibly useful for creating reusable, configurable closures from general-purpose ones. Think of it as a closure factory: you start with a flexible closure and stamp out specialized versions by locking in specific arguments.

In this post, we will cover Groovy’s three currying methods (curry, rcurry, ncurry), walk through 10 tested examples with actual output, and explore real-world patterns where currying makes your code cleaner and more expressive.

What is Currying?

Currying is named after mathematician Haskell Curry (who also gave his name to the Haskell programming language). In pure functional programming, currying means transforming a function that takes multiple arguments into a chain of functions that each take a single argument. For example, a function f(a, b, c) becomes f(a)(b)(c).

Groovy takes a slightly more practical approach. What Groovy calls “currying” is technically partial application — you fix one or more arguments of a closure and get back a new closure that takes the remaining arguments. The distinction matters to computer science purists, but for day-to-day programming, the result is the same: you get specialized closures from general ones.

According to the official Groovy documentation on currying, Groovy provides three methods for this purpose: curry(), rcurry(), and ncurry().

curry, rcurry, and ncurry at a Glance

Here is a quick summary of the three currying methods before we jump into examples:

MethodWhat It DoesFixes Arguments From
curry(args...)Fixes leftmost argumentsLeft side (first, second, …)
rcurry(args...)Fixes rightmost argumentsRight side (…, second-to-last, last)
ncurry(index, args...)Fixes arguments starting at a specific indexAny position by index

All three methods return a new closure. The original closure is never modified. You can curry a curried closure again to fix even more arguments — they compose naturally.

Curry Methods Quick Reference

// Original: 3 parameters
def greet = { greeting, title, name -> "${greeting}, ${title} ${name}!" }

// curry() fixes from the left
def hello = greet.curry("Hello")           // fixes greeting="Hello"
println hello("Dr.", "Smith")              // Hello, Dr. Smith!

// rcurry() fixes from the right
def forSmith = greet.rcurry("Smith")       // fixes name="Smith"
println forSmith("Hi", "Mr.")             // Hi, Mr. Smith!

// ncurry() fixes at a specific index
def withDr = greet.ncurry(1, "Dr.")        // fixes title="Dr." at index 1
println withDr("Hey", "Jones")            // Hey, Dr. Jones!

Output

Hello, Dr. Smith!
Hi, Mr. Smith!
Hey, Dr. Jones!

10 Practical Examples

Let us explore currying with real, practical code. Every example has been tested on Groovy 5.x with actual output shown.

Example 1: Basic curry() – Fix the Leftmost Arguments

The curry() method fixes arguments from the left side. It is the most commonly used currying method because we often want to specialize the first parameter of a general closure.

Example 1: Basic curry()

// A general-purpose math operation closure
def multiply = { a, b -> a * b }

// Create specialized versions by currying the first argument
def double_ = multiply.curry(2)
def triple = multiply.curry(3)
def tenTimes = multiply.curry(10)

println "double(5):   ${double_(5)}"
println "triple(5):   ${triple(5)}"
println "tenTimes(5): ${tenTimes(5)}"

// The original closure is unchanged
println "\nOriginal: ${multiply(7, 8)}"

// Currying multiple arguments at once
def add3 = { a, b, c -> a + b + c }
def add5and10 = add3.curry(5, 10)
println "\n5 + 10 + 20 = ${add5and10(20)}"

// The curried closure is a real closure -- you can pass it around
def numbers = [1, 2, 3, 4, 5]
println "Doubled list: ${numbers.collect(double_)}"
println "Tripled list: ${numbers.collect(triple)}"

// Type of a curried closure
println "\nType: ${double_.getClass().simpleName}"
println "Is a Closure: ${double_ instanceof Closure}"

Output

double(5):   10
triple(5):   15
tenTimes(5): 50

Original: 56

5 + 10 + 20 = 35
Doubled list: [2, 4, 6, 8, 10]
Tripled list: [3, 6, 9, 12, 15]

Type: CurriedClosure
Is a Closure: true

Notice that curried closures are full Closure objects. You can pass them to collect, findAll, sort, or any other method that accepts closures. They just happen to have fewer required parameters than the original.

Example 2: rcurry() – Fix the Rightmost Arguments

The rcurry() method fixes arguments from the right side. It is useful when the last parameter is the one you want to specialize, while keeping the earlier parameters flexible.

Example 2: rcurry()

// A power function: base raised to exponent
def power = { base, exponent -> base ** exponent }

// Fix the exponent from the right
def square = power.rcurry(2)
def cube = power.rcurry(3)
def fourthPower = power.rcurry(4)

println "3 squared:  ${square(3)}"
println "3 cubed:    ${cube(3)}"
println "3 to 4th:   ${fourthPower(3)}"

// A formatter closure: format a value with a suffix
def format = { value, suffix -> "${value}${suffix}" }

// Fix the suffix
def asPercent = format.rcurry("%")
def asDollars = format.rcurry(" USD")
def asKg = format.rcurry(" kg")

println "\nFormatted: ${asPercent(95)}"
println "Formatted: ${asDollars(42.50)}"
println "Formatted: ${asKg(75)}"

// Use curried closures in collection operations
def prices = [10.5, 20.0, 35.75, 8.99]
def formattedPrices = prices.collect(asDollars)
println "\nPrices: ${formattedPrices}"

// rcurry with multiple arguments
def describe = { adj, noun, verb -> "The ${adj} ${noun} ${verb}." }
def runsAway = describe.rcurry("runs away")
println "\n${runsAway('quick', 'fox')}"
println "${runsAway('lazy', 'dog')}"

Output

3 squared:  9
3 cubed:    27
3 to 4th:   81

Formatted: 95%
Formatted: 42.50 USD
Formatted: 75 kg

Prices: [10.5 USD, 20.0 USD, 35.75 USD, 8.99 USD]

The quick fox runs away.
The lazy dog runs away.

The rcurry method is particularly handy for formatter closures and template patterns where the “shape” of the output is the last parameter.

Example 3: ncurry() – Fix Arguments at Any Position

When you need to fix a parameter in the middle of the argument list, ncurry() is your tool. The first argument is the zero-based index where the fixed values should be inserted.

Example 3: ncurry()

// A closure with three parameters
def sendMessage = { from, protocol, to ->
    println "${from} --[${protocol}]--> ${to}"
}

// Fix the protocol (middle parameter, index 1)
def sendViaHttp = sendMessage.ncurry(1, "HTTP")
def sendViaGrpc = sendMessage.ncurry(1, "gRPC")
def sendViaMqtt = sendMessage.ncurry(1, "MQTT")

sendViaHttp("ClientA", "ServerX")
sendViaGrpc("ServiceB", "ServiceY")
sendViaMqtt("SensorC", "BrokerZ")

// Fix the middle parameter in a 4-param closure
def logEntry = { timestamp, level, source, message ->
    "[${timestamp}] ${level} (${source}): ${message}"
}

// Fix the level at index 1
def infoLog = logEntry.ncurry(1, "INFO")
def errorLog = logEntry.ncurry(1, "ERROR")

println "\n${infoLog('2026-03-08', 'App', 'Server started')}"
println "${errorLog('2026-03-08', 'DB', 'Connection refused')}"

// ncurry with multiple values at an index
def table = { col1, col2, col3, col4 ->
    "| ${col1} | ${col2} | ${col3} | ${col4} |"
}

// Fix columns 1 and 2 (index 1, two values)
def withMiddleFixed = table.ncurry(1, "FIXED-A", "FIXED-B")
println "\n${withMiddleFixed('Start', 'End')}"
println "${withMiddleFixed('Alpha', 'Omega')}"

Output

ClientA --[HTTP]--> ServerX
ServiceB --[gRPC]--> ServiceY
SensorC --[MQTT]--> BrokerZ

[2026-03-08] INFO (App): Server started
[2026-03-08] ERROR (DB): Connection refused

| Start | FIXED-A | FIXED-B | End |
| Alpha | FIXED-A | FIXED-B | Omega |

The ncurry method is the most flexible of the three. While curry and rcurry cover the majority of use cases, ncurry handles the edge case where the parameter you need to fix is somewhere in the middle.

Example 4: Chained Currying – Fixing Arguments Incrementally

You can curry a curried closure. Each call to curry() fixes one more argument, progressively reducing the number of required parameters until you have a zero-argument closure.

Example 4: Chained Currying

// Start with a 4-parameter closure
def sendEmail = { from, to, subject, body ->
    """\
    |From:    ${from}
    |To:      ${to}
    |Subject: ${subject}
    |Body:    ${body}""".stripMargin()
}

// Step 1: Fix the sender
def fromAdmin = sendEmail.curry("admin@technoscripts.com")

// Step 2: Fix the recipient
def adminToUser = fromAdmin.curry("user@example.com")

// Step 3: Fix the subject
def welcomeEmail = adminToUser.curry("Welcome!")

// Step 4: Now only the body remains
println welcomeEmail("Thanks for signing up!")
println "\n---\n"

// You can use any of the intermediate curried closures
println fromAdmin("boss@company.com", "Report Ready", "Q1 report is attached.")
println "\n---\n"
println adminToUser("Password Reset", "Click the link to reset your password.")

// Check parameter counts at each stage
println "\n--- Parameter Counts ---"
println "Original:       ${sendEmail.maximumNumberOfParameters}"
println "fromAdmin:      ${fromAdmin.maximumNumberOfParameters}"
println "adminToUser:    ${adminToUser.maximumNumberOfParameters}"
println "welcomeEmail:   ${welcomeEmail.maximumNumberOfParameters}"

Output

From:    admin@technoscripts.com
To:      user@example.com
Subject: Welcome!
Body:    Thanks for signing up!

---

From:    admin@technoscripts.com
To:      boss@company.com
Subject: Report Ready
Body:    Q1 report is attached.

---

From:    admin@technoscripts.com
To:      user@example.com
Subject: Password Reset
Body:    Click the link to reset your password.

--- Parameter Counts ---
Original:       4
fromAdmin:      3
adminToUser:    2
welcomeEmail:   1

Each currying step produces a new closure with one fewer required parameter. You can use the maximumNumberOfParameters property to check how many arguments a closure still needs. This is useful for writing generic code that adapts to closures of different arities.

Example 5: Building a Configurable Logger with Curry

One of the most practical uses of currying is building configurable utility closures. Here is a logging system where you create specialized loggers by currying a general-purpose log closure.

Example 5: Configurable Logger

// General-purpose log closure
def log = { String level, String component, String message ->
    def timestamp = new Date().format("yyyy-MM-dd HH:mm:ss")
    println "[${timestamp}] ${level.padRight(5)} [${component}] ${message}"
}

// Create level-specific loggers
def info = log.curry("INFO")
def warn = log.curry("WARN")
def error = log.curry("ERROR")
def debug = log.curry("DEBUG")

// Create component-specific loggers
def dbInfo = info.curry("Database")
def dbError = error.curry("Database")
def apiInfo = info.curry("API")
def apiWarn = warn.curry("API")

// Use them -- clean and descriptive
dbInfo("Connection pool initialized with 10 connections")
dbInfo("Query executed in 42ms")
dbError("Failed to connect to replica")
apiInfo("Endpoint /users registered")
apiWarn("Rate limit approaching: 950/1000 requests")

println "\n--- Direct use of level loggers ---"
info("Scheduler", "Cron job started")
error("Auth", "Invalid token received")
debug("Cache", "Cache miss for key: user_42")

Output

[2026-03-09 16:20:11] INFO  [Database] Connection pool initialized with 10 connections
[2026-03-09 16:20:11] INFO  [Database] Query executed in 42ms
[2026-03-09 16:20:11] ERROR [Database] Failed to connect to replica
[2026-03-09 16:20:11] INFO  [API] Endpoint /users registered
[2026-03-09 16:20:11] WARN  [API] Rate limit approaching: 950/1000 requests

--- Direct use of level loggers ---
[2026-03-

This is a clean alternative to creating separate logger classes or using complex configuration files. You define the logging logic once and stamp out specialized versions with curry(). Each specialized logger is a simple closure that takes just one string argument — the message.

Example 6: Creating Validators with Curry

Currying is excellent for building a library of validation closures from a few generic ones.

Example 6: Validators with Curry

// Generic range validator
def inRange = { min, max, value -> value >= min && value <= max }

// Create specific validators
def isValidAge = inRange.curry(0, 150)
def isValidPercent = inRange.curry(0, 100)
def isValidPort = inRange.curry(1, 65535)
def isValidRgb = inRange.curry(0, 255)

println "Age 25 valid:    ${isValidAge(25)}"
println "Age -5 valid:    ${isValidAge(-5)}"
println "Percent 101:     ${isValidPercent(101)}"
println "Port 8080:       ${isValidPort(8080)}"
println "RGB 200:         ${isValidRgb(200)}"
println "RGB 300:         ${isValidRgb(300)}"

// Generic length validator
def hasLength = { min, max, String str ->
    str.length() >= min && str.length() <= max
}

def isValidUsername = hasLength.curry(3, 20)
def isValidPassword = hasLength.curry(8, 128)
def isValidTweet = hasLength.curry(1, 280)

println "\n--- String Validators ---"
println "Username 'ab':           ${isValidUsername('ab')}"
println "Username 'alice':        ${isValidUsername('alice')}"
println "Password '1234567':      ${isValidPassword('1234567')}"
println "Password '12345678':     ${isValidPassword('12345678')}"

// Combine validators
def validateUser = { String name, int age ->
    def errors = []
    if (!isValidUsername(name)) errors << "Invalid username"
    if (!isValidAge(age)) errors << "Invalid age"
    errors.isEmpty() ? "Valid!" : "Errors: ${errors.join(', ')}"
}

println "\n--- Combined Validation ---"
println "alice, 25: ${validateUser('alice', 25)}"
println "ab, 25:    ${validateUser('ab', 25)}"
println "alice, -1: ${validateUser('alice', -1)}"
println "ab, 200:   ${validateUser('ab', 200)}"

Output

Age 25 valid:    true
Age -5 valid:    false
Percent 101:     false
Port 8080:       true
RGB 200:         true
RGB 300:         false

--- String Validators ---
Username 'ab':           false
Username 'alice':        true
Password '1234567':      false
Password '12345678':     true

--- Combined Validation ---
alice, 25: Valid!
ab, 25:    Errors: Invalid username
alice, -1: Errors: Invalid age
ab, 200:   Errors: Invalid username, Invalid age

Instead of writing separate validation methods for each data type, you write one generic validator and curry it into specific ones. This is the DRY principle applied through functional programming.

Example 7: Currying with Collection Operations

Curried closures work well with Groovy’s collection methods. You can pass them directly to collect, findAll, sort, and any other method that accepts a closure.

Example 7: Currying with Collections

// Multiplier closure
def multiply = { factor, value -> factor * value }

// Create specialized transformers
def double_ = multiply.curry(2)
def triple = multiply.curry(3)
def halve = multiply.curry(0.5)

def numbers = [10, 20, 30, 40, 50]

println "Original:  ${numbers}"
println "Doubled:   ${numbers.collect(double_)}"
println "Tripled:   ${numbers.collect(triple)}"
println "Halved:    ${numbers.collect(halve)}"

// Filter with curried closures
def greaterThan = { threshold, value -> value > threshold }
def lessThan = { threshold, value -> value < threshold }

def gt25 = greaterThan.curry(25)
def lt45 = lessThan.curry(45)

println "\n> 25:      ${numbers.findAll(gt25)}"
println "< 45:      ${numbers.findAll(lt45)}"
println "> 25 AND < 45: ${numbers.findAll(gt25).findAll(lt45)}"

// String transformations with curry
def wrap = { String before, String after, String content ->
    "${before}${content}${after}"
}

def inParens = wrap.curry("(").rcurry(")")
def inBrackets = wrap.curry("[").rcurry("]")
def bold = wrap.curry("<b>").rcurry("</b>")

def words = ["hello", "world", "groovy"]
println "\nIn parens:   ${words.collect(inParens)}"
println "In brackets: ${words.collect(inBrackets)}"
println "Bold:        ${words.collect(bold)}"

Output

Original:  [10, 20, 30, 40, 50]
Doubled:   [20, 40, 60, 80, 100]
Tripled:   [30, 60, 90, 120, 150]
Halved:    [5.0, 10.0, 15.0, 20.0, 25.0]

> 25:      [30, 40, 50]
< 45:      [10, 20, 30, 40]
> 25 AND < 45: [30, 40]

In parens:   [()hello, ()world, ()groovy]
In brackets: [[]hello, []world, []groovy]
Bold:        [hello, world, groovy]

This example shows the beautiful interplay between currying and collection operations. You define general-purpose closures once and then curry them into specific versions that plug directly into collect, findAll, and other higher-order functions. No lambda boilerplate, no anonymous classes.

Example 8: Closure Composition with Curry

Groovy closures support composition with the << (left-shift) and >> (right-shift) operators. When combined with currying, you can build powerful processing pipelines.

Example 8: Composition with Curry

// Basic closure composition
def add = { a, b -> a + b }
def multiply = { a, b -> a * b }
def negate = { x -> -x }
def double_ = { x -> x * 2 }

// Compose: first double, then negate
def doubleAndNegate = negate << double_
println "doubleAndNegate(5): ${doubleAndNegate(5)}"  // -10

// Using >> (reverse composition): double then negate
def sameResult = double_ >> negate
println "sameResult(5):      ${sameResult(5)}"       // -10

// Combining curry with composition
def addTen = add.curry(10)
def multiplyByThree = multiply.curry(3)

// Pipeline: add 10, then multiply by 3
def addThenMultiply = multiplyByThree << addTen
println "\naddThenMultiply(5):  ${addThenMultiply(5)}"  // (5+10)*3 = 45

// Reverse: multiply by 3, then add 10
def multiplyThenAdd = addTen << multiplyByThree
println "multiplyThenAdd(5):  ${multiplyThenAdd(5)}"    // (5*3)+10 = 25

// Build a text processing pipeline
def trim = { String s -> s.strip() }
def lower = { String s -> s.toLowerCase() }
def removeSpaces = { String s -> s.replaceAll(/\s+/, '-') }
def addPrefix = { String prefix, String s -> "${prefix}${s}" }

def slugify = (addPrefix.curry("/")) << removeSpaces << lower << trim

println "\nSlugify examples:"
println slugify("  Hello World  ")
println slugify("  Groovy Curry Tutorial  ")
println slugify(" My Blog Post Title ")

Output

doubleAndNegate(5): -10
sameResult(5):      -10

addThenMultiply(5):  45
multiplyThenAdd(5):  25

Slugify examples:
/hello-world
/groovy-curry-tutorial
/my-blog-post-title

The << operator composes right-to-left (like mathematical function composition: f << g means f(g(x))). The >> operator composes left-to-right (pipeline style: f >> g means g(f(x))). Combined with curry, you can build reusable processing pipelines from small, composable pieces.

Example 9: Curried Event Handlers and Callbacks

Currying is perfect for creating event handler factories where you want to bind some context at registration time and receive event data at dispatch time.

Example 9: Event Handlers

// Simple event system
class EventBus {
    Map<String, List<Closure>> handlers = [:].withDefault { [] }

    void on(String event, Closure handler) {
        handlers[event] << handler
    }

    void emit(String event, Map data = [:]) {
        handlers[event].each { handler -> handler(data) }
    }
}

// General-purpose handler closure
def handleEvent = { String component, String action, Map data ->
    println "[${component}] ${action}: ${data}"
}

// Create curried handlers for different components
def userHandler = handleEvent.curry("UserService")
def orderHandler = handleEvent.curry("OrderService")

// Further specialize with specific actions
def onUserCreated = userHandler.curry("created")
def onUserDeleted = userHandler.curry("deleted")
def onOrderPlaced = orderHandler.curry("placed")
def onOrderShipped = orderHandler.curry("shipped")

// Register handlers
def bus = new EventBus()
bus.on("user.created", onUserCreated)
bus.on("user.deleted", onUserDeleted)
bus.on("order.placed", onOrderPlaced)
bus.on("order.shipped", onOrderShipped)

// Emit events
bus.emit("user.created", [id: 42, name: "Alice"])
bus.emit("order.placed", [orderId: "ORD-001", total: 99.99])
bus.emit("user.deleted", [id: 42, reason: "requested"])
bus.emit("order.shipped", [orderId: "ORD-001", carrier: "FedEx"])

Output

[UserService] created: [id:42, name:Alice]
[OrderService] placed: [orderId:ORD-001, total:99.99]
[UserService] deleted: [id:42, reason:requested]
[OrderService] shipped: [orderId:ORD-001, carrier:FedEx]

The beauty here is that one general-purpose handler closure generates all your specialized handlers. Adding a new component or action is just another curry() call — no new classes, no inheritance, no boilerplate.

Example 10: Combining Curry with Memoization

Groovy closures support memoize() for caching results. You can combine memoization with currying to create cached specialized closures.

Example 10: Curry + Memoize

// An "expensive" computation
def compute = { String operation, int x, int y ->
    println "  Computing ${operation}(${x}, ${y})..."
    Thread.sleep(100)  // Simulate expensive work
    switch (operation) {
        case "add": return x + y
        case "mul": return x * y
        case "pow": return x ** y
        default: return 0
    }
}

// Create a memoized version first, then curry
def cachedCompute = compute.memoize()

def cachedAdd = cachedCompute.curry("add")
def cachedMul = cachedCompute.curry("mul")
def cachedPow = cachedCompute.curry("pow")

println "First calls (will compute):"
println "add(3, 4) = ${cachedAdd(3, 4)}"
println "mul(3, 4) = ${cachedMul(3, 4)}"
println "pow(2, 10) = ${cachedPow(2, 10)}"

println "\nSecond calls (cached - no 'Computing...' message):"
println "add(3, 4) = ${cachedAdd(3, 4)}"
println "mul(3, 4) = ${cachedMul(3, 4)}"
println "pow(2, 10) = ${cachedPow(2, 10)}"

println "\nNew arguments (will compute):"
println "add(5, 6) = ${cachedAdd(5, 6)}"

// Useful pattern: memoized fibonacci with curry
def fib
fib = { BigInteger n ->
    n <= 1 ? n : fib(n - 1) + fib(n - 2)
}.memoize()

println "\nFibonacci(30): ${fib(30)}"
println "Fibonacci(50): ${fib(50)}"

Output

First calls (will compute):
  Computing add(3, 4)...
add(3, 4) = 7
  Computing mul(3, 4)...
mul(3, 4) = 12
  Computing pow(2, 10)...
pow(2, 10) = 1024

Second calls (cached - no 'Computing...' message):
add(3, 4) = 7
mul(3, 4) = 12
pow(2, 10) = 1024

New arguments (will compute):
  Computing add(5, 6)...
add(5, 6) = 11

Fibonacci(30): 832040
Fibonacci(50): 12586269025

The order matters: apply memoize() first, then curry(). This way, the cache is shared across all curried variants. If you curry first and then memoize, each curried closure gets its own separate cache.

Currying vs Partial Application

Although Groovy uses the name “curry,” what it actually implements is partial application. Here is the technical difference:

ConceptTrue CurryingPartial Application (Groovy)
What it doesTransforms f(a,b,c) into f(a)(b)(c)Fixes some arguments: f(a,b,c) -> g(c) where a,b are fixed
ResultChain of single-argument functionsA function with fewer arguments
Can fix multiple args at once?No, always one at a timeYes, curry(a, b) fixes two at once
Can fix args from the right?NoYes, via rcurry()
LanguagesHaskell, OCaml (automatically)Groovy, Scala, JavaScript (manually)

For practical purposes, the distinction rarely matters. Groovy’s curry() does what you expect: it fixes arguments and gives you back a simpler closure. The official Groovy documentation uses the term “currying” for this feature, so we will too.

Real-World Patterns

Here are the most common patterns where currying adds real value in production Groovy code:

Pattern 1: Configuration Factories

Configuration Factory Pattern

// Database query builder
def query = { String table, String condition, String orderBy ->
    "SELECT * FROM ${table} WHERE ${condition} ORDER BY ${orderBy}"
}

def userQuery = query.curry("users")
def activeUsers = userQuery.curry("active = true")

println activeUsers("last_login DESC")
println activeUsers("name ASC")
println userQuery("age > 18", "name")

Output

SELECT * FROM users WHERE active = true ORDER BY last_login DESC
SELECT * FROM users WHERE active = true ORDER BY name ASC
SELECT * FROM users WHERE age > 18 ORDER BY name

Pattern 2: Unit Converters

Unit Converter Pattern

def convert = { double factor, double offset, double value ->
    value * factor + offset
}

def celsiusToFahrenheit = convert.curry(9.0/5.0, 32)
def kmToMiles = convert.curry(0.621371, 0)
def kgToLbs = convert.curry(2.20462, 0)

println "100C = ${celsiusToFahrenheit(100)}F"
println "42km = ${String.format('%.2f', kmToMiles(42))} miles"
println "80kg = ${String.format('%.2f', kgToLbs(80))} lbs"

// Apply to lists
def tempsC = [0, 20, 37, 100]
def tempsF = tempsC.collect(celsiusToFahrenheit)
println "\nCelsius:    ${tempsC}"
println "Fahrenheit: ${tempsF}"

Output

100C = 212.0F
42km = 26.10 miles
80kg = 176.37 lbs

Celsius:    [0, 20, 37, 100]
Fahrenheit: [32.0, 68.0, 98.60000000000001, 212.0]

Pattern 3: Retry Logic

Retry Pattern

def withRetry = { int maxRetries, long delayMs, Closure action ->
    int attempts = 0
    while (attempts < maxRetries) {
        try {
            return action()
        } catch (Exception e) {
            attempts++
            if (attempts >= maxRetries) throw e
            println "  Attempt ${attempts} failed: ${e.message}. Retrying in ${delayMs}ms..."
            Thread.sleep(delayMs)
        }
    }
}

// Create specialized retry policies
def quickRetry = withRetry.curry(3, 100)    // 3 retries, 100ms delay
def patientRetry = withRetry.curry(5, 1000) // 5 retries, 1s delay

// Simulate a flaky operation
int callCount = 0
def flakyOperation = {
    callCount++
    if (callCount < 3) throw new RuntimeException("Server busy")
    return "Success on attempt ${callCount}!"
}

println quickRetry(flakyOperation)

Output

Attempt 1 failed: Server busy. Retrying in 100ms...
  Attempt 2 failed: Server busy. Retrying in 100ms...
Success on attempt 3!

Common Pitfalls

Watch out for these common mistakes when using currying in Groovy:

  • Currying too many arguments — if you curry more arguments than the closure has parameters, you will get a runtime error. The closure’s maximumNumberOfParameters property helps you check.
  • Wrong order with memoize — always call memoize() before curry() if you want all curried versions to share the same cache.
  • Confusing curry and rcurrycurry fills from the left, rcurry fills from the right. If your arguments end up in wrong positions, you probably used the wrong method.
  • Losing type information — curried closures lose the parameter type metadata from the original closure. If you rely on @CompileStatic, be aware that type checking is weaker for curried closures.
  • Over-currying — not everything needs to be curried. If you find yourself currying a closure just to immediately call it, you are adding complexity without benefit. Curry when you need to reuse the specialized version multiple times.

Conclusion

We have covered the full spectrum of Groovy curry and partial application. From the basic curry() that fixes leftmost arguments, through rcurry() for rightmost arguments, to ncurry() for positional fixing — you now have the tools to create specialized closures from general-purpose ones without writing redundant code.

Currying is at its best when combined with other functional features: composition operators (<< and >>), memoization, and higher-order collection methods. Together, they let you write Groovy code that is concise, composable, and easy to test.

Summary

  • curry() fixes arguments from the left — the most common currying method
  • rcurry() fixes arguments from the right — useful for formatters and suffixes
  • ncurry(index, args) fixes arguments at any position by zero-based index
  • Curried closures are real Closure objects — pass them to collect, findAll, and any closure-accepting method
  • Chain curry() calls to fix arguments incrementally
  • Combine with memoize() for cached specialized closures (memoize first, then curry)
  • Use composition operators (<< and >>) with curried closures to build processing pipelines
  • Groovy’s “currying” is technically partial application, but the practical effect is the same

If you also work with build tools, CI/CD pipelines, or cloud CLIs, check out Command Playground to practice 105+ CLI tools directly in your browser — no install needed.

Up next: Groovy Method References and Method Pointers

Frequently Asked Questions

What is curry() in Groovy?

curry() is a method on Groovy closures that performs partial application. It fixes one or more leftmost arguments of a closure and returns a new closure that accepts the remaining arguments. For example, if you have def add = { a, b -> a + b }, then add.curry(10) returns a new closure that only needs one argument and always adds 10 to it. The original closure is not modified.

What is the difference between curry, rcurry, and ncurry in Groovy?

curry() fixes arguments from the left (first parameter onward). rcurry() fixes arguments from the right (last parameter backward). ncurry() fixes arguments at a specific zero-based index position. For example, with a closure { a, b, c -> … }: curry(x) fixes a=x, rcurry(x) fixes c=x, and ncurry(1, x) fixes b=x. All three return a new closure with fewer required parameters.

Is Groovy curry the same as currying in Haskell?

Not exactly. In Haskell, currying transforms a multi-argument function into a chain of single-argument functions automatically. Groovy’s curry() is technically partial application — it fixes some arguments and returns a function that takes the rest. However, the Groovy documentation and community use the term ‘currying’ for this feature. The practical result is similar: you get specialized functions from general ones.

Can I curry a curried closure in Groovy?

Yes, you can chain curry calls. Each call fixes more arguments and returns a new closure with fewer required parameters. For example: def f = { a, b, c -> a + b + c }; def g = f.curry(1); def h = g.curry(2); println h(3) prints 6. You can check the remaining parameter count with closure.maximumNumberOfParameters.

How do I combine curry with memoize in Groovy?

Call memoize() first, then curry(). This way all curried versions share the same cache. Example: def cached = expensiveOp.memoize(); def specialized = cached.curry('param1'). If you curry first and then memoize, each curried closure gets its own independent cache, which is usually not what you want. The memoize() method caches results based on all arguments including the curried ones.

Previous in Series: Groovy Higher-Order Functions – collect, inject, groupBy

Next in Series: Groovy Method References and Method Pointers

Related Topics You Might Like:

Happy currying with Groovy!

This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

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