[python] 함수형 프로그래밍에서의 예외 처리 방법에 대해 알려주세요.

함수형 프로그래밍은 부작용을 최소화하고 불변성을 유지하는 프로그래밍 패러다임입니다. 예외 처리는 프로그램의 안정성과 신뢰성을 보장하기 위해 중요한 부분입니다. 이번 포스트에서는 함수형 프로그래밍에서의 예외 처리 방법에 대해 알아보겠습니다.

1. 예외 대신 Either 타입 사용하기

예외 처리를 위해 함수형 프로그래밍에서는 일반적으로 Either 타입을 사용합니다. Either는 두 가지 가능한 값 중 하나를 나타내는 대수적 자료형입니다. 예외가 발생하지 않으면 Right 값을 가지고, 예외가 발생하면 Left 값을 가집니다.

from typing import Either

def divide(a: int, b: int) -> Either[str, float]:
    if b == 0:
        return Either.Left("Division by zero is not allowed")
    return Either.Right(a / b)

위의 코드에서 divide 함수는 두 개의 정수를 입력받아서 나눈 값을 반환하는 함수입니다. 만약 b 값이 0이면 예외가 발생하고, 그렇지 않으면 나눈 값이 Right 값으로 반환됩니다.

2. 패턴 매칭을 통한 예외 처리

함수형 프로그래밍에서는 패턴 매칭을 통해 예외 처리를 할 수 있습니다. Either 타입의 값을 패턴 매칭하여 각각의 경우에 대한 처리를 수행할 수 있습니다.

result = divide(10, 5)
match result:
    case Either.Left(error):
        print(f"Error: {error}")
    case Either.Right(value):
        print(f"Result: {value}")

위의 코드에서 divide 함수를 호출한 결과를 result 변수에 저장하고, 이를 패턴 매칭하여 예외 처리를 수행합니다. 예외가 발생한 경우에는 error 변수에 예외 메시지가 할당되고, 그렇지 않은 경우에는 value 변수에 결과 값이 할당됩니다.

3. 중첩된 예외 처리

함수형 프로그래밍에서는 중첩된 예외 처리를 위해 MonadEither를 사용할 수 있습니다. 중첩된 예외 처리는 여러 과정을 거쳐 발생할 수 있는 예외를 효율적으로 다룰 수 있게 해줍니다.

def validate_age(age: int) -> Either[str, int]:
    if age < 0:
        return Either.Left("Age cannot be negative")
    return Either.Right(age)

def calculate_discount(age: int) -> Either[str, float]:
    result = validate_age(age)
    match result:
        case Either.Left(error):
            return Either.Left(error)
        case Either.Right(valid_age):
            if valid_age < 18:
                return Either.Right(0.2)
            else:
                return Either.Right(0.1)

위의 코드에서, validate_age 함수는 나이를 검증하는 함수이고, calculate_discount 함수는 나이에 따라 할인율을 계산하는 함수입니다. calculate_discount 함수에서는 validate_age 함수를 호출한 결과로부터 Either 값을 받아오고, 이를 패턴 매칭하여 예외 처리를 할 수 있습니다.

4. 합성 함수를 통한 예외 처리

함수형 프로그래밍에서는 합성 함수를 통해 예외 처리를 할 수 있습니다. 예외가 발생한 함수를 다른 함수로 감싸서 예외를 처리하고, 처리된 결과를 반환할 수 있습니다.

from functools import wraps

def handle_exception(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            return f"Error: {str(e)}"
    return wrapper

@handle_exception
def divide(a: int, b: int) -> float:
    return a / b

위의 코드에서는 handle_exception 데코레이터 함수를 정의하고, divide 함수에 이를 적용하여 예외 처리를 수행합니다. divide 함수의 실행 중에 예외가 발생하면, handle_exception 함수에서 해당 예외를 처리하고 에러 메시지를 반환합니다.

마치며

함수형 프로그래밍에서의 예외 처리 방법에 대해 살펴보았습니다. Either 타입이나 패턴 매칭을 통해 예외를 처리하고, 중첩된 예외 처리를 가능하게 하며, 합성 함수를 통해 예외를 처리할 수 있습니다. 이러한 방법들을 통해 함수형 프로그래밍에서도 안정적이고 신뢰성 있는 예외 처리를 할 수 있습니다.