|
| 1 | +package org.nkcoder.fp; |
| 2 | + |
| 3 | +import java.util.function.BiFunction; |
| 4 | +import java.util.function.Function; |
| 5 | +import java.util.function.IntFunction; |
| 6 | +import java.util.function.UnaryOperator; |
| 7 | + |
| 8 | +/** |
| 9 | + * Currying and Partial Application in Java. |
| 10 | + * |
| 11 | + * <ul> |
| 12 | + * <li>Currying: Transform multi-arg function into chain of single-arg functions</li> |
| 13 | + * <li>Partial Application: Fix some arguments, return new function</li> |
| 14 | + * <li>Function Factories: Create specialized functions from general ones</li> |
| 15 | + * <li>Enables better code reuse and composition</li> |
| 16 | + * </ul> |
| 17 | + */ |
| 18 | +public class CurryingExample { |
| 19 | + |
| 20 | + static void main(String[] args) { |
| 21 | + whatIsCurrying(); |
| 22 | + partialApplication(); |
| 23 | + functionFactories(); |
| 24 | + realWorldExamples(); |
| 25 | + } |
| 26 | + |
| 27 | + // ============ What is Currying ============ |
| 28 | + |
| 29 | + static void whatIsCurrying() { |
| 30 | + System.out.println("=== What is Currying ==="); |
| 31 | + |
| 32 | + // Traditional: function with 2 arguments |
| 33 | + BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b; |
| 34 | + System.out.println("add(3, 5) = " + add.apply(3, 5)); |
| 35 | + |
| 36 | + // Curried: chain of single-argument functions |
| 37 | + // f(a, b) becomes f(a)(b) |
| 38 | + Function<Integer, Function<Integer, Integer>> curriedAdd = a -> b -> a + b; |
| 39 | + System.out.println("curriedAdd(3)(5) = " + curriedAdd.apply(3).apply(5)); |
| 40 | + |
| 41 | + // Step by step |
| 42 | + Function<Integer, Integer> add3 = curriedAdd.apply(3); // Partially apply first arg |
| 43 | + System.out.println("add3(5) = " + add3.apply(5)); |
| 44 | + System.out.println("add3(10) = " + add3.apply(10)); |
| 45 | + |
| 46 | + // Curried function with 3 arguments |
| 47 | + Function<Integer, Function<Integer, Function<Integer, Integer>>> curriedSum3 = |
| 48 | + a -> b -> c -> a + b + c; |
| 49 | + System.out.println("curriedSum3(1)(2)(3) = " + curriedSum3.apply(1).apply(2).apply(3)); |
| 50 | + |
| 51 | + // Generic curry helper |
| 52 | + System.out.println("\nUsing curry helper:"); |
| 53 | + BiFunction<String, String, String> concat = (a, b) -> a + b; |
| 54 | + var curriedConcat = curry(concat); |
| 55 | + System.out.println("curriedConcat(\"Hello \")(\"World\") = " + |
| 56 | + curriedConcat.apply("Hello ").apply("World")); |
| 57 | + |
| 58 | + System.out.println(""" |
| 59 | +
|
| 60 | + Currying transforms f(a, b) into f(a)(b) |
| 61 | + - Each function takes ONE argument |
| 62 | + - Returns a function for the next argument |
| 63 | + - Named after Haskell Curry (mathematician) |
| 64 | + """); |
| 65 | + } |
| 66 | + |
| 67 | + // Helper: Convert BiFunction to curried form |
| 68 | + static <A, B, R> Function<A, Function<B, R>> curry(BiFunction<A, B, R> f) { |
| 69 | + return a -> b -> f.apply(a, b); |
| 70 | + } |
| 71 | + |
| 72 | + // Helper: Convert curried function back to BiFunction |
| 73 | + static <A, B, R> BiFunction<A, B, R> uncurry(Function<A, Function<B, R>> f) { |
| 74 | + return (a, b) -> f.apply(a).apply(b); |
| 75 | + } |
| 76 | + |
| 77 | + // ============ Partial Application ============ |
| 78 | + |
| 79 | + static void partialApplication() { |
| 80 | + System.out.println("=== Partial Application ==="); |
| 81 | + |
| 82 | + // Original function |
| 83 | + TriFunction<String, String, String, String> formatMessage = |
| 84 | + (level, timestamp, message) -> "[" + level + "] " + timestamp + ": " + message; |
| 85 | + |
| 86 | + String fullMessage = formatMessage.apply("INFO", "2024-01-15", "Server started"); |
| 87 | + System.out.println("Full: " + fullMessage); |
| 88 | + |
| 89 | + // Partial application: fix the level |
| 90 | + BiFunction<String, String, String> infoLogger = partial1(formatMessage, "INFO"); |
| 91 | + BiFunction<String, String, String> errorLogger = partial1(formatMessage, "ERROR"); |
| 92 | + |
| 93 | + System.out.println("Info: " + infoLogger.apply("2024-01-15", "User logged in")); |
| 94 | + System.out.println("Error: " + errorLogger.apply("2024-01-15", "Connection failed")); |
| 95 | + |
| 96 | + // Partial application: fix level and timestamp |
| 97 | + Function<String, String> todayInfoLogger = partial2(formatMessage, "INFO", "2024-01-15"); |
| 98 | + System.out.println("Today Info: " + todayInfoLogger.apply("Processing complete")); |
| 99 | + |
| 100 | + // Using currying for partial application |
| 101 | + Function<String, Function<String, Function<String, String>>> curriedFormat = |
| 102 | + level -> timestamp -> message -> "[" + level + "] " + timestamp + ": " + message; |
| 103 | + |
| 104 | + var debugLogger = curriedFormat.apply("DEBUG"); |
| 105 | + var debugTodayLogger = debugLogger.apply("2024-01-15"); |
| 106 | + System.out.println("Debug Today: " + debugTodayLogger.apply("Cache hit")); |
| 107 | + |
| 108 | + System.out.println(""" |
| 109 | +
|
| 110 | + Partial Application fixes some arguments: |
| 111 | + - f(a, b, c) with a fixed becomes g(b, c) |
| 112 | + - Creates specialized versions of general functions |
| 113 | + - Different from currying (which chains single-arg functions) |
| 114 | + """); |
| 115 | + } |
| 116 | + |
| 117 | + // Custom TriFunction interface |
| 118 | + @FunctionalInterface |
| 119 | + interface TriFunction<A, B, C, R> { |
| 120 | + R apply(A a, B b, C c); |
| 121 | + } |
| 122 | + |
| 123 | + // Partial application helpers |
| 124 | + static <A, B, C, R> BiFunction<B, C, R> partial1(TriFunction<A, B, C, R> f, A a) { |
| 125 | + return (b, c) -> f.apply(a, b, c); |
| 126 | + } |
| 127 | + |
| 128 | + static <A, B, C, R> Function<C, R> partial2(TriFunction<A, B, C, R> f, A a, B b) { |
| 129 | + return c -> f.apply(a, b, c); |
| 130 | + } |
| 131 | + |
| 132 | + // ============ Function Factories ============ |
| 133 | + |
| 134 | + static void functionFactories() { |
| 135 | + System.out.println("=== Function Factories ==="); |
| 136 | + |
| 137 | + // Multiplier factory |
| 138 | + IntFunction<UnaryOperator<Integer>> multiplier = factor -> x -> x * factor; |
| 139 | + var double_ = multiplier.apply(2); |
| 140 | + var triple = multiplier.apply(3); |
| 141 | + |
| 142 | + System.out.println("double(5) = " + double_.apply(5)); |
| 143 | + System.out.println("triple(5) = " + triple.apply(5)); |
| 144 | + |
| 145 | + // Adder factory |
| 146 | + Function<Integer, UnaryOperator<Integer>> adder = n -> x -> x + n; |
| 147 | + var add10 = adder.apply(10); |
| 148 | + var add100 = adder.apply(100); |
| 149 | + |
| 150 | + System.out.println("add10(5) = " + add10.apply(5)); |
| 151 | + System.out.println("add100(5) = " + add100.apply(5)); |
| 152 | + |
| 153 | + // Comparator factory |
| 154 | + Function<Integer, Function<Integer, Boolean>> greaterThan = threshold -> value -> value > threshold; |
| 155 | + var greaterThan10 = greaterThan.apply(10); |
| 156 | + var greaterThan100 = greaterThan.apply(100); |
| 157 | + |
| 158 | + System.out.println("greaterThan10(15) = " + greaterThan10.apply(15)); |
| 159 | + System.out.println("greaterThan100(15) = " + greaterThan100.apply(15)); |
| 160 | + |
| 161 | + // Prefix/suffix factory |
| 162 | + Function<String, UnaryOperator<String>> prefixer = prefix -> s -> prefix + s; |
| 163 | + Function<String, UnaryOperator<String>> suffixer = suffix -> s -> s + suffix; |
| 164 | + |
| 165 | + var addHello = prefixer.apply("Hello, "); |
| 166 | + var addBang = suffixer.apply("!"); |
| 167 | + var greet = addHello.andThen(addBang); |
| 168 | + |
| 169 | + System.out.println("greet(\"World\") = " + greet.apply("World")); |
| 170 | + |
| 171 | + // Compose factories |
| 172 | + var pipeline = double_.andThen(add10).andThen(triple); |
| 173 | + System.out.println("pipeline(5) = (5*2 + 10) * 3 = " + pipeline.apply(5)); |
| 174 | + |
| 175 | + System.out.println(""" |
| 176 | +
|
| 177 | + Function Factories create specialized functions: |
| 178 | + - Take configuration, return function |
| 179 | + - Enable code reuse and DRY principle |
| 180 | + - Compose well with andThen/compose |
| 181 | + """); |
| 182 | + } |
| 183 | + |
| 184 | + // ============ Real World Examples ============ |
| 185 | + |
| 186 | + static void realWorldExamples() { |
| 187 | + System.out.println("=== Real World Examples ==="); |
| 188 | + |
| 189 | + // 1. URL Builder |
| 190 | + System.out.println("-- URL Builder --"); |
| 191 | + Function<String, Function<String, Function<String, String>>> urlBuilder = |
| 192 | + protocol -> host -> path -> protocol + "://" + host + path; |
| 193 | + |
| 194 | + var httpsBuilder = urlBuilder.apply("https"); |
| 195 | + var apiBuilder = httpsBuilder.apply("api.example.com"); |
| 196 | + |
| 197 | + System.out.println(apiBuilder.apply("/users")); |
| 198 | + System.out.println(apiBuilder.apply("/products")); |
| 199 | + |
| 200 | + // 2. Validator Factory |
| 201 | + System.out.println("\n-- Validator Factory --"); |
| 202 | + Function<Integer, Function<Integer, Function<String, String>>> rangeValidator = |
| 203 | + min -> max -> value -> { |
| 204 | + try { |
| 205 | + int num = Integer.parseInt(value); |
| 206 | + return (num >= min && num <= max) ? "Valid" : "Out of range [" + min + "-" + max + "]"; |
| 207 | + } catch (NumberFormatException e) { |
| 208 | + return "Not a number"; |
| 209 | + } |
| 210 | + }; |
| 211 | + |
| 212 | + var ageValidator = rangeValidator.apply(0).apply(150); |
| 213 | + var percentValidator = rangeValidator.apply(0).apply(100); |
| 214 | + |
| 215 | + System.out.println("Age '25': " + ageValidator.apply("25")); |
| 216 | + System.out.println("Age '200': " + ageValidator.apply("200")); |
| 217 | + System.out.println("Percent '50': " + percentValidator.apply("50")); |
| 218 | + |
| 219 | + // 3. String Formatter Factory |
| 220 | + System.out.println("\n-- String Formatter Factory --"); |
| 221 | + Function<String, Function<String, UnaryOperator<String>>> wrapper = |
| 222 | + prefix -> suffix -> s -> prefix + s + suffix; |
| 223 | + |
| 224 | + var htmlBold = wrapper.apply("<b>").apply("</b>"); |
| 225 | + var htmlItalic = wrapper.apply("<i>").apply("</i>"); |
| 226 | + var parentheses = wrapper.apply("(").apply(")"); |
| 227 | + |
| 228 | + System.out.println(htmlBold.apply("important")); |
| 229 | + System.out.println(htmlItalic.apply("emphasized")); |
| 230 | + System.out.println(parentheses.apply("note")); |
| 231 | + |
| 232 | + // 4. Discount Calculator |
| 233 | + System.out.println("\n-- Discount Calculator --"); |
| 234 | + Function<Double, UnaryOperator<Double>> discountBy = |
| 235 | + percent -> price -> price * (1 - percent / 100); |
| 236 | + |
| 237 | + var tenPercentOff = discountBy.apply(10.0); |
| 238 | + var twentyPercentOff = discountBy.apply(20.0); |
| 239 | + var halfPrice = discountBy.apply(50.0); |
| 240 | + |
| 241 | + double originalPrice = 100.0; |
| 242 | + System.out.printf("Original: $%.2f%n", originalPrice); |
| 243 | + System.out.printf("10%% off: $%.2f%n", tenPercentOff.apply(originalPrice)); |
| 244 | + System.out.printf("20%% off: $%.2f%n", twentyPercentOff.apply(originalPrice)); |
| 245 | + System.out.printf("50%% off: $%.2f%n", halfPrice.apply(originalPrice)); |
| 246 | + |
| 247 | + System.out.println(""" |
| 248 | +
|
| 249 | + Real-world uses of currying/partial application: |
| 250 | + - Configuration builders (URLs, queries) |
| 251 | + - Validation with configurable rules |
| 252 | + - Text formatting and templating |
| 253 | + - Price calculators with configurable discounts |
| 254 | + - Logger factories with preset levels |
| 255 | + """); |
| 256 | + } |
| 257 | +} |
0 commit comments