# Currying & Partial Application in C# ## Overview Currying transforms a function that takes multiple arguments into a sequence of functions, each accepting a single argument. Partial application fixes one or more arguments of a function, producing a new function with fewer parameters. While C# is not a functional-first language, these patterns are directly expressible with `Func<>` delegates and lambda expressions. The `Curryfy` NuGet package and similar libraries provide extension methods to curry standard `Func<>` delegates automatically, but the patterns can also be implemented manually for full control. ## Currying vs Partial Application | Concept | Description | Result | |---------|-------------|--------| | Currying | Transforms `f(a, b, c)` into `f(a)(b)(c)` | Chain of single-argument functions | | Partial application | Fixes some arguments of `f(a, b, c)` | New function with fewer parameters | | Composition | Chains `f` and `g` into `g(f(x))` | Single function from two functions | ## Basic Currying ```csharp // Traditional multi-argument function static decimal CalculatePrice(decimal basePrice, decimal taxRate, decimal discount) => (basePrice * (1 + taxRate)) - discount; // Manual currying: transform to chain of single-argument functions static Func> CurriedPrice(decimal basePrice) => taxRate => discount => (basePrice * (1 + taxRate)) - discount; // Usage var priceWith20Tax = CurriedPrice(100m)(0.20m); var finalPrice = priceWith20Tax(10m); // (100 * 1.20) - 10 = 110 // Generic curry extension method public static class CurryExtensions { public static Func> Curry( this Func func) => a => b => func(a, b); public static Func>> Curry( this Func func) => a => b => c => func(a, b, c); public static Func>>> Curry( this Func func) => a => b => c => d => func(a, b, c, d); } // Using the extension Func calcPrice = CalculatePrice; var curried = calcPrice.Curry(); var withBase = curried(100m); var withTax = withBase(0.20m); var result = withTax(10m); // 110 ``` ## Partial Application ```csharp public static class PartialExtensions { // Fix first argument public static Func Partial( this Func func, T1 arg1) => arg2 => func(arg1, arg2); // Fix first two arguments public static Func Partial( this Func func, T1 arg1, T2 arg2) => arg3 => func(arg1, arg2, arg3); // Fix first argument of three-argument function public static Func Partial( this Func func, T1 arg1) => (arg2, arg3) => func(arg1, arg2, arg3); } // Practical example: database query factory Func> searchOrders = (customerId, page, pageSize) => db.Orders .Where(o => o.CustomerId == customerId) .Skip((page - 1) * pageSize) .Take(pageSize) .ToList(); // Create specialized query for a specific customer var customerOrders = searchOrders.Partial("cust-123"); var page1 = customerOrders(1, 20); var page2 = customerOrders(2, 20); ``` ## Function Composition ```csharp public static class ComposeExtensions { // Compose: g(f(x)) public static Func Compose( this Func g, Func f) => x => g(f(x)); // Pipe: f(x) then g public static Func Then( this Func f, Func g) => x => g(f(x)); } // Example: composing string transformations Func trim = s => s.Trim(); Func toLower = s => s.ToLowerInvariant(); Func removeSpaces = s => s.Replace(" ", "-"); var slugify = trim .Then(toLower) .Then(removeSpaces); var slug = slugify(" Hello World "); // "hello-world" ``` ## Practical Patterns ### Configuration Builders ```csharp // Curried configuration for creating HTTP clients Func>> createClient = baseUrl => timeout => maxRetries => { var handler = new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(5) }; var client = new HttpClient(handler) { BaseAddress = new Uri(baseUrl), Timeout = timeout }; client.DefaultRequestHeaders.Add("X-Max-Retries", maxRetries.ToString()); return client; }; // Partially apply environment-specific settings var productionClient = createClient("https://api.example.com")(TimeSpan.FromSeconds(30)); var devClient = createClient("https://localhost:5001")(TimeSpan.FromSeconds(5)); // Final application var apiClient = productionClient(3); var testClient = devClient(0); ``` ### Validation Pipelines ```csharp Func notEmpty = s => !string.IsNullOrWhiteSpace(s); Func maxLength100 = s => s.Length <= 100; Func validEmail = s => s.Contains('@') && s.Contains('.'); // Compose validators public static Func And( this Func left, Func right) => x => left(x) && right(x); var validateEmail = notEmpty .And(maxLength100) .And(validEmail); var isValid = validateEmail("user@example.com"); // true var isInvalid = validateEmail(""); // false ``` ### Middleware-Like Pipelines ```csharp // Build a processing pipeline with curried decorators Func>, Func>> withLogging = next => async order => { Console.WriteLine($"Processing order {order.Id}"); var result = await next(order); Console.WriteLine($"Completed order {order.Id}"); return result; }; Func>, Func>> withValidation = next => async order => { if (order.Total <= 0) throw new ValidationException("Invalid total"); return await next(order); }; Func> processOrder = async order => { order.Status = OrderStatus.Processed; return order; }; // Compose the pipeline var pipeline = withLogging(withValidation(processOrder)); var result = await pipeline(new Order { Id = 1, Total = 50m }); ``` ### Dependency Injection Factories ```csharp // Use partial application to create service factories Func createService = (logger, connection, region) => new OrderService(logger, connection, region); // In DI registration, partially apply shared dependencies builder.Services.AddScoped(sp => { var logger = sp.GetRequiredService>(); var connection = sp.GetRequiredService(); var factory = createService.Partial(logger).Partial(connection); return factory("us-east-1"); }); ``` ## Curryfy NuGet Package ```csharp // The Curryfy package provides automatic currying for Func<> and Action<> // Install: dotnet add package Curryfy using Curryfy; Func add3 = (a, b, c) => a + b + c; // Auto-curry var curried = add3.Curry(); // Func>> var result = curried(1)(2)(3); // 6 // Partial application var add1 = add3.Partial(1); // Func var add1and2 = add3.Partial(1, 2); // Func ``` ## Best Practices - Use currying to create families of related functions from a single general function, fixing environment or configuration parameters early (e.g., `createLogger("orders")` returns a specialized logger). - Prefer partial application over currying when you only need to fix one or two arguments and the remaining function still takes multiple parameters. - Use function composition (`Then`/`Compose`) to build data transformation pipelines that are more readable than nested function calls. - Keep curried functions focused: if a function takes more than 3-4 parameters, consider refactoring into an options record or builder pattern instead. - Document the expected argument order in curried functions, since the first argument is fixed first and determines the specialization hierarchy. - Use currying for middleware and decorator patterns where each layer wraps the next, creating composable processing pipelines. - Combine currying with generic type constraints to build type-safe fluent APIs that guide the developer through required configuration steps. - Avoid excessive currying in team codebases unfamiliar with functional patterns; provide XML documentation and named factory methods as wrappers for discoverability. - Test curried functions at each level of application to verify that partially applied functions behave correctly with different fixed arguments. - Use the `Curryfy` NuGet package for automatic curry/partial extension methods on standard `Func<>` delegates rather than implementing your own for every arity.