# 프로그래밍 패러다임 흐름 훑고가기 - 함수형 프로그래밍

# 함수형 프로그래밍

# 개요

기존 객체 지향 프로그래밍에서 가지는 문제를 해결하는 대안으로 함수형 프로그래밍이 주목받고 있습니다.

  • 함수의 비일관성: 객체의 멤버 변수가 변경될 경우 함수(메소드)가 다른 결과를 반환할 수 있습니다.
  • 객체간 의존성 문제: 객체 간 상호작용을 위해서 다른 객체의 함수들을 호출하게 되고, 이는 자연스럽게 프로그램 복잡도를 증가시킵니다.
  • 객체 내 상태 제어의 어려움: 외부에서 객체의 상태를 변경하는 메소드들이 호출되면, 언제 어디서 객체의 상태를 변경했는지 추적이 어렵습니다.

함수형(Functional) 프로그래밍은 외부 상태를 갖지 않는 함수들의 연속으로 프로그래밍을 하는 패러다임입니다.

외부 상태를 갖지 않는다는 의미는, 같은 입력을 넣었을 때 언제나 같은 출력을 내보낸다는 것입니다. 즉 함수의 입/출력에 영향을 주는 외부 요인이 없습니다.

함수형 프로그래밍 코드에서는 한 번 초기화한 변수는 변하지 않습니다. 이런 특성을 불변성이라고 하는데, 이 불변성을 통해 안정성을 얻을 수 있습니다.

# 기본 개념

함수형 프로그래밍에서는 함수가 외부 상태를 갖지 않는다는 것이 중요합니다. 예를 들면 다음은 함수형 프로그래밍에서 이야기하는 순수 함수가 아닙니다.

c = 1

def func(a: int, b: int) -> int:
    return a + b + c  # c 라는 외부 값, 상태가 포함되어 있기에 함수형이 아닙니다.

반면 다음은 순수 함수라고 볼 수 있습니다.

def func(a: int, b: int, c: int) -> int:
    return a + b + c  # 주어진 파라미터만 사용합니다. 별도의 상태가 없습니다.

수학에서 f(x) = y 라는 함수 f 가 있을 때, fx 를 입력으로 주면 항상 y 라는 출력을 얻습니다. 이처럼 함수형 프로그래밍은 같은 입력을 주었을 때 항상 같은 값을 출력하도록 상태를 갖지 않는 것입니다.

그렇다면 왜 외부 상태를 가지지 않으려고 할까요? 일반적으로 통제하지 못하는 외부 상태를 사용한다면 예측하지 못한 결과를 가질 수 있기 때문입니다.

예를 들어 다음과 같이 상태를 가지는 Calculator 클래스가 있습니다.

class Calculator:
    def __init__(self, alpha: int) -> None:
        self.alpha = alpha
        self.beta = 0

    def run() -> int
    	return self.alpha + self.beta

위 코드에서 run() 메서드 실행 시 매번 같은 값이 나오리라는 보장은 없습니다. 예를 들면 다음처럼 말이죠.

calculator = Calculator(alpha=3)
calculator.run()  # 3 반환
calculator.beta = 1
calculator.run()  # 4 반환

이렇게 매번 상태를 가지면, 같은 함수를 실행하더라도 실제로 내가 의도하는 결과가 나오는지 완전히 단언하기 어렵습니다. 위 코드처럼 상태는 어디서든 바뀔 가능성이 있기 때문이죠. 보통 이런 현상을 사이트 이펙트라고 합니다. 객체 지향 언어들도 이런 상태가 존재함으로써 생기는 문제를 알고 있어 접근 제어자(private, protected)와 같은 기능들을 제공하지만, 완벽한 해법은 아닙니다.


# 예시

함수형 프로그래밍은 보통 다음과 같은 순서로 문제를 해결하게 됩니다.

  1. 문제를 잘개 쪼갭니다.
  2. 쪼개진 문제들 중 하나를 해결하는 순수 함수를 만듭니다.
  3. 순수 함수들을 결합하여 문제들을 해결해 나갑니다.

보통 함수형 프로그래밍에서는 함수를 조합하는 방식으로 Pipelining, Partial Application, Currying 등이 있습니다.

# Pipelining
def main():
    pipe_func = pipe(read_input_file, parse_input_data, save_data)
    return pipe_func("input_file.txt")


# Partial application
def power(base, exp): #powering
    return base ** exp

def main():
    square = partial(power, exp=2)
    cube = partial(power, exp=3)
    square(2) #2의 제곱인 4 반환
    cube(2) #2의 세제곱인 8 반환

프로그래밍 언어 중에서는 대표적으로 '스칼라(Scala)'가 이런 함수형 프로그래밍을 따르는 언어이며 다른 프로그래밍 언어에서도 특정 API를 사용하면 함수형 프로그래밍이 가능합니다.

# 장단점

함수형 프로그래밍은 상태로 인한 사이드 이펙트가 없기 때문에 안정적입니다. 따라서 동시성을 가지는 프로그램에 사용하기 적합합니다. 특히 대용량 데이터를 병렬적으로 처리할 때 이렇게 사이드 이펙트가 없도록 로직을 설계하는 것은 매우 중요한데, 최근 데이터 처리기술의 발전으로 함수형 프로그래밍이 부상되었습니다.

하지만 실제로 함수형 프로그래밍을 하기 위해선, 상태를 허용하지 않기에 기존 객체 지향과 같은 기능의 코드를 구현하려면 다양한 함수들을 조합해서 사용해야 합니다. 그리고 친숙하지 않은 설계 방식으로 인해 러닝 커브가 높습니다.


# 정리

  • 프로그래밍 패러다임이란 프로그래밍을 어떤 기준으로 바라보고 작성할 것인지에 대한 관점입니다.
  • 절차지향 프로그래밍은 순차적인 함수 호출을 중심의 관점입니다.
    • 주로 구조가 TOP-DOWN 입니다. 따라서 이해하기 쉽습니다.
    • 하지만 코드를 확장하거나 자주 실행에 따라 로직이 바뀌어야 하는 경우에 수정하기 어렵습니다.
  • 객체 지향 프로그래밍은 객체들의 책임과 협력을 중심의 관점입니다.
    • 다형성과 의존성 주입으로 코드를 확장하기 쉬우며, 실행환경의 다양한 입력에 대응하기 좋습니다.
    • 하지만 런타임이 되기 전에 실제로 코드가 어떤 방향으로 흐르는지 알기 어려우며, 디버깅도 어렵습니다.
  • 함수형 프로그래밍은 상태를 갖지 않는 함수들의 활용 중심의 관점입니다.
    • 상태를 가지지 않기 때문에 예측에서 벗어나는 결과(사이드 이펙트)가 없습니다.
    • 하지만 실제로 상태를 가지지 않는 함수를 작성하고 활용하는 코드를 작성하는 것은 어렵습니다.

# 더 읽어보면 좋은 것들

Last Updated: 2/20/2022, 1:51:31 PM

CC-BY-NC-ND-4.0 Licensed | Copyright © 2021-present Grab