개발 기록

>함수형 프로그래밍은 무엇인가? 본문

DIVE

>함수형 프로그래밍은 무엇인가?

1z 2024. 2. 26. 16:11

 

 

1. 개요 

●  프로그래밍은 컴퓨터에게 수행할 작업을 지시하는 과정을 말하며, 여러가지 패러다임 또는 방식으로 분류할 수 있다.

 

① 선언형 프로그래밍: '무엇(What)'을 표현하는 방식에 중점을 둔다. 수행해야 할 작업을 선언하고, 어떻게 처리할지 명시한다.

ex. SQL, Prolog (논리 프로그래밍 언어), 함수형 프로그래밍 언어

② 명령형 프로그래밍: '어떻게(How)'를 표현하는 방식에 중점을 둔다. 수행해야 할 작업을 세부적인 명령어로 순차적으로 기술

ex. 프로시저기반 언어 (예: C, Pascal), 객체지향 프로그래밍 언어의 일부 (예: Java, C++), 절차지향 프로그래밍

③ 객체지향 프로그래밍: 객체(object)를 기반으로 프로그램을 구성하고 작성하는 방식.  

ex. Java, C++, Python, Ruby

④ 함수형 프로그래밍: 수학적 함수의 개념을 기반으로 프로그램을 작성. 

ex : Haskell, Lisp, Scala, JavaScript (일부 기능)

⑤ 절차지향 프로그래밍: 프로시저나 함수의 연속적인 호출에 중점을 둔다. 구조를 절차적으로 기능 단위로 분해하여 작성한다.

ex. C, Pascal

⑥ 로직형 프로그래밍: 사실과 규칙을 기반으로 프로그램을 작성하고 문제를 해결하는 방식에 중점을 둔다 

ex. Prolog

⑦ 반응형 프로그래밍: 데이터의 흐름에 따라 시스템을 구성하고, 데이터의 변화에 반응하여 동작하는 프로그래밍 방식을 강조한다.

ex. RxJava, Reactive Extensions (ReactiveX)

 

이 중 함수형 프로그래밍에 대해서 알아보자!!!!

 

2. 함수형 프로그래밍 

프로그램을 수학적인 함수의 평가로 간주하고, 함수의 조합을 통해 프로그램을 구성하는 프로그래밍 패러다임

 

(1) 특징 

불변성 데이터: 데이터를 변경할 수 없는 불변 객체로 다룬다. 데이터의 상태를 변경하는 대신, 새로운 데이터를 생성하거나 변형한다. (ex. final 키워드)

함수의 일급 객체 : 함수를 자료구조에 저장하고, 함수를 다른 함수의 인자로 전달하거나 다른 함수의 반환 값으로 사용

순수 함수: 순수 함수는 동일한 입력에 대해 항상 동일한 출력을 반환하며, 외부의 상태를 변경하지 않는다.

고차 함수: 함수를 다루는 함수. 고차 함수는 다른 함수를 인자로 받거나 함수를 반환할 수 있다.

재귀: 재귀를 통해 반복을 대체하고, 재귀적으로 문제를 분해하여 해결한다.

모나드: 부수 효과(side effect)를 다루는 데 사용되는 개념적인 구조. (ex. List, Future, IO..등)

참조 투명성: 동일한 인자에 대해 항상 동일한 결과를 반환한다.

 


함수형 프로그래밍은 초반에는 큰 호응을 받지 못했지만, 병렬 처리와 이벤트 지향 프로그래밍에 적합하기 때문에 다시 부각 되고 있다. 그래서 더욱 효율적인 프로그래밍을 위해 객체 지향 프로그래밍과 함수적 프로그래밍을 혼합해서 사용하고 있다.

2. JAVA 에서 함수형 프로그래밍 

 

(1) 함수의 일급 객체(First-Class Functions)

함수의 일급 객체란 프로그래밍 언어에서 함수를 다루는데 있어서 일반적인 값과 동일하게 취급되는 개체를 의미.

 

함수를 변수에 할당할 수 있다.=> 함수를 으로 취급

함수를 다른 함수의 인자로 전달할 수 있다. => 고차함수를 사용할 수있는 기반이 된다. 

함수를 다른 함수의 반환 값으로 사용할 수 있다. => 함수가 함수를 생성하거나 반환할 수 있다.

함수를 자료구조에 저아할 수 있다. => 함수를 배열, 리스트, 맵 등의 자료 구조에 저장할 수 있다.

 

* Java에서 함수형 인터페이스를 사용하여 함수를 일급 객체로 다룰 수 있다.

 

함수형 인터페이스 (Functional Interface):

java.util.function 패키지에 다양한 함수형 인터페이스가 포함되어 있다.(Function, Predicate, Supplier, Consumer 등)

@FunctionalInterface 함수형 인터페이스를 선언하기 위한 어노테이션
interface Function<T,R> 하나의 인수를 받아들이고 결과를 생성하는 함수를 나타냄
@param <T> 함수에 대한 입력 유형
@param <R> 함수 결과의 유형 타입
apply 메서드 특정 함수에 대해 주어진 인자(입력값)를 사용하여 연산 또는 처리를 수행
 

Function interface

 

 

function 인터페이스를 통해 함수형 프로그래밍을 구현해보자!

import java.util.function.Function;

public class FunctionExample {
    public static void main(String[] args) {
        // Function 인터페이스를 구현한 두 수의 합을 계산하는 함수
        Function<Integer, Integer> addTwo = new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer x) {
                return x + 2;
            }
        };

        // Function을 사용하여 값을 계산
        int result = addTwo.apply(3); // 입력값 3을 넣어서 계산
        System.out.println("Result: " + result); // 출력: 5
    }
}

 

 

위의 예제를 람다 표현식으로 바꾸면 더 간단하게 구현할 수 있다.

import java.util.function.Function;

public class LambdaFunctionExample {
    public static void main(String[] args) {
        // 람다 표현식을 사용하여 Function 인터페이스 구현
        Function<Integer, Integer> addTwo = x -> x + 2;

        // Function을 사용하여 값을 계산
        int result = addTwo.apply(3); // 입력값 3을 넣어서 계산
        System.out.println("Result: " + result); // 출력: 5
    }
}

 

 

※ 모든 함수형 프로그래밍 코드가 함수형 인터페이스를 구현해야 하는 것은 아니며, 람다 표현식과 스트림 API 등의 기능만으로도 함수형 프로그래밍의 장점을 활용할 수 있다.

 

(2) 고차 함수 

고차 함수는 다른 함수를 인자로 받거나 함수를 반환하는 함수를 의미한다.(ex. 함수 합성) 즉 함수를 다루는 함수로 함수형 프로그래밍에서 함수를 조합하고 추상화하기 위해 사용된다. 

import java.util.function.Function;

public class HigherOrderFunctionExample {

    // 고차 함수: 다른 함수를 인자로 받아서 실행하는 함수
    // Function<Integer, Integer> func: Integer 타입을 입력으로 받아 Integer 타입을 반환
    static void applyFunction(int x, Function<Integer, Integer> func) {
        int result = func.apply(x); // func에 전달된 함수를 x에 적용하여 결과 계산
        System.out.println("Result: " + result);
    }

    public static void main(String[] args) {
        // 고차 함수를 호출하여 다양한 함수를 전달해 보기
        applyFunction(5, y -> y * 2); // 입력값에 2를 곱하는 함수 전달 => 결과 5 * 2 = 10 출력
        applyFunction(8, z -> z + 10); // 입력값에 10을 더하는 함수 전달 => 결과 8 + 10 = 18이 출력
    }
}

 

(3) 메서드 레퍼런스

메서드 레퍼런스는 Java에서 메서드를 가리키는 표현식으로, 람다 표현식을 간결하게 표현할 수 있는 방법을 제공한다.

' :: ' 기호를 사용하여 특정 메서드를 참조한다

 

예를 들어, 아래 예제는 MethodReferenceExample::convertToUpper 구문을 사용하여 정적 메서드 convertToUpper를 참조하고, 이를 Function<String, String> 인터페이스의 구현체로 사용하고 있다. 

import java.util.function.Function;

public class MethodReferenceExample {
    // 정적 메서드
    static String convertToUpper(String str) {
        return str.toUpperCase();
    }

    public static void main(String[] args) {
        // 메서드 레퍼런스를 사용하여 정적 메서드를 Function 인터페이스에 전달
        Function<String, String> toUpperCase = MethodReferenceExample::convertToUpper;

        // Function을 사용하여 값을 변환
        String result = toUpperCase.apply("hello");
        System.out.println("Result: " + result); // 출력: "HELLO"
    }
}

 

 

(4) 모나드    

모나드는 값을 래핑하고, 일련의 변환을 적용하고, 모든 변환이 적용된 값을 다시 가져올 수 있도록 해준다.

Java에는 Optional과 CompletableFuture 등  모나드적인 특성을 가진 클래스가 있다. 

Optional.of(2).flatMap(f -> Optional.of(3).flatMap(s -> Optional.of(f + s)))
 

 

 

참고

https://www.baeldung.com/java-functional-programming

https://medium.com/gitconnected/monoids-in-java-functional-programming-for-o-o-p-developers-46972ff33d87

https://itstory1592.tistory.com/120