Pink Spider/Curring in Java

Created Fri, 20 Jun 2025 14:11:00 +0900 Modified Mon, 08 Dec 2025 08:41:47 +0900
1102 Words 5 min

1. 함수형 인터페이스 사용 (Java 8+)

기본적인 커링 구현

import java.util.function.Function;

public class CurryingExample {
    
    // 3개 인자를 받는 함수를 커링
    public static Function<Integer, Function<Integer, Function<Integer, Integer>>> 
           curriedAdd() {
        return a -> b -> c -> a + b + c;
    }
    
    public static void main(String[] args) {
        // 사용 예
        Function<Integer, Function<Integer, Function<Integer, Integer>>> add = curriedAdd();
        
        int result1 = add.apply(1).apply(2).apply(3); // 6
        System.out.println(result1);
        
        // 부분 적용
        Function<Integer, Function<Integer, Integer>> add5 = add.apply(5);
        Function<Integer, Integer> add5And3 = add5.apply(3);
        
        int result2 = add5And3.apply(2); // 10
        System.out.println(result2);
    }
}

더 복잡한 예제 - 문자열 처리

import java.util.function.Function;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StringCurrying {
    
    // 문자열 포함 검사 함수
    public static Function<String, Function<String, Boolean>> contains() {
        return substring -> str -> str.contains(substring);
    }
    
    // 문자열 변환 함수
    public static Function<String, Function<String, String>> replace() {
        return target -> replacement -> str -> str.replace(target, replacement);
    }
    
    public static void main(String[] args) {
        List<String> frameworks = Arrays.asList("React", "Angular", "Vue", "Svelte");
        
        // 부분 적용된 함수 생성
        Function<String, Boolean> hasReact = contains().apply("React");
        
        // 스트림과 함께 사용
        List<String> reactItems = frameworks.stream()
            .filter(hasReact::apply)
            .collect(Collectors.toList());
        
        System.out.println(reactItems); // [React]
        
        // 문자열 치환
        Function<String, String> replaceJavaWithKotlin = 
            replace().apply("Java").apply("Kotlin");
        
        String result = replaceJavaWithKotlin.apply("I love Java programming");
        System.out.println(result); // I love Kotlin programming
    }
}

2. 제네릭을 활용한 범용 커링 유틸리티

import java.util.function.*;

public class CurryUtil {
    
    // 2개 인자 함수 커링
    public static <A, B, R> Function<A, Function<B, R>> 
           curry(BiFunction<A, B, R> function) {
        return a -> b -> function.apply(a, b);
    }
    
    // 3개 인자 함수를 위한 함수형 인터페이스
    @FunctionalInterface
    public interface TriFunction<A, B, C, R> {
        R apply(A a, B b, C c);
    }
    
    // 3개 인자 함수 커링
    public static <A, B, C, R> Function<A, Function<B, Function<C, R>>> 
           curry(TriFunction<A, B, C, R> function) {
        return a -> b -> c -> function.apply(a, b, c);
    }
    
    public static void main(String[] args) {
        // BiFunction 커링
        BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
        Function<Integer, Function<Integer, Integer>> curriedMultiply = curry(multiply);
        
        Function<Integer, Integer> double_ = curriedMultiply.apply(2);
        System.out.println(double_.apply(5)); // 10
        
        // TriFunction 커링
        TriFunction<Integer, Integer, Integer, Integer> add3 = (a, b, c) -> a + b + c;
        Function<Integer, Function<Integer, Function<Integer, Integer>>> curriedAdd3 = curry(add3);
        
        int result = curriedAdd3.apply(1).apply(2).apply(3);
        System.out.println(result); // 6
    }
}

3. 클래스 기반 구현

public class MathOperations {
    
    public static class CurriedAdd {
        private final int first;
        
        private CurriedAdd(int first) {
            this.first = first;
        }
        
        public static CurriedAdd of(int first) {
            return new CurriedAdd(first);
        }
        
        public CurriedAddSecond with(int second) {
            return new CurriedAddSecond(first, second);
        }
        
        public class CurriedAddSecond {
            private final int second;
            
            private CurriedAddSecond(int first, int second) {
                this.second = second;
            }
            
            public int and(int third) {
                return first + second + third;
            }
        }
    }
    
    public static void main(String[] args) {
        int result = CurriedAdd.of(1).with(2).and(3); // 6
        System.out.println(result);
        
        // 부분 적용
        CurriedAdd add5 = CurriedAdd.of(5);
        CurriedAdd.CurriedAddSecond add5And3 = add5.with(3);
        
        int result2 = add5And3.and(2); // 10
        System.out.println(result2);
    }
}

4. 실용적인 예제 - HTTP 클라이언트

import java.util.function.Function;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;

public class CurriedHttpClient {
    
    public static Function<String, Function<String, Function<String, HttpRequest>>> 
           httpRequest() {
        return method -> url -> body -> {
            HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(url));
            
            switch (method.toUpperCase()) {
                case "GET":
                    builder.GET();
                    break;
                case "POST":
                    builder.POST(HttpRequest.BodyPublishers.ofString(body));
                    break;
                case "PUT":
                    builder.PUT(HttpRequest.BodyPublishers.ofString(body));
                    break;
            }
            
            return builder.build();
        };
    }
    
    public static void main(String[] args) {
        // 부분 적용된 함수들 생성
        Function<String, Function<String, HttpRequest>> get = 
            httpRequest().apply("GET");
        Function<String, Function<String, HttpRequest>> post = 
            httpRequest().apply("POST");
        
        // 사용
        HttpRequest getRequest = get.apply("https://api.example.com/users").apply("");
        HttpRequest postRequest = post.apply("https://api.example.com/users")
            .apply("{\"name\":\"김철수\"}");
        
        System.out.println("GET: " + getRequest.uri());
        System.out.println("POST: " + postRequest.uri());
    }
}

5. Vavr 라이브러리 사용

Vavr(이전 Javaslang)는 자바에서 함수형 프로그래밍을 더 쉽게 할 수 있게 해주는 라이브러리입니다:

// Maven 의존성 필요: io.vavr:vavr:0.10.4
import io.vavr.Function2;
import io.vavr.Function3;

public class VavrCurrying {
    public static void main(String[] args) {
        // Function2 커링
        Function2<Integer, Integer, Integer> add = (a, b) -> a + b;
        Function1<Integer, Function1<Integer, Integer>> curriedAdd = add.curried();
        
        int result1 = curriedAdd.apply(5).apply(3); // 8
        
        // Function3 커링
        Function3<Integer, Integer, Integer, Integer> add3 = (a, b, c) -> a + b + c;
        Function1<Integer, Function1<Integer, Function1<Integer, Integer>>> curriedAdd3 = 
            add3.curried();
        
        int result2 = curriedAdd3.apply(1).apply(2).apply(3); // 6
        
        System.out.println(result1); // 8
        System.out.println(result2); // 6
    }
}

자바 커링의 한계와 특징

장점:

  • Java 8+ 람다와 함수형 인터페이스로 구현 가능
  • 타입 안정성 보장
  • 부분 적용을 통한 코드 재사용성 향상

단점:

  • JavaScript에 비해 문법이 복잡하고 장황함
  • 제네릭 타입 선언이 복잡해질 수 있음
  • 함수 체이닝이 길어지면 가독성이 떨어질 수 있음
  • 성능 오버헤드 (객체 생성, 메서드 호출 증가)

자바에서 커링은 가능하지만, 언어의 특성상 JavaScript만큼 자연스럽지는 않습니다. 하지만 함수형 프로그래밍 패러다임을 적용하고 코드의 재사용성을 높이는 데는 유용한 기법입니다.