18장: 함수형 프로그래밍
함수형 프로그래밍
side effect
공유 가변 데이터 구조를 사용하면 프로그램 전체에서 데이터가 갱신되었다는 사실을 추적하기 어려워진다.
예를 들어 여러 클래스에서 공유하는 가변 리스트가 있다면, 어느 클래스가 소유자인지 구분하기 어렵고 어느 클래스가 데이터를 추가/수정했는지 알기 어렵다.
자료구조의 데이터를 수정하거나 필드에 값을 할당하는 것, 예외, 파일에 I/O 동작을 수행하는 것 등등을 side effect라고 부른다.
이러한 side effect를 없앨 수 있도록 불변 객체를 사용할 수 있으며, side effect 없는 시스템 컴포넌트는 메서드가 서로 간섭하는 일이 없으므로 lock 없이도 멀티코어 병렬성을 사용할 수 있다.
프로그래밍 기법
선언형 프로그래밍
무엇을 어떻게 수행할 것인지에 대해 집중하는 방식이다.
문제를 어떻게 푸는지 명확히 보여주는 스트림의 내부 반복 방식도 선언형 프로그래밍에 해당한다.
함수형 프로그래밍
선언형 프로그래밍을 따르는 대표적인 방식이며, side effect가 없는 방식을 지향한다.
여러 연산을 연결해 복잡한 질의를 표현하고 자연스럽게 읽고 쓸 수 있는 코드 구현에 적합하다.
객체 지향 프로그래밍
모든 것을 객체로 간주하여 프로그램이 객체의 필드를 갱신하거나 메서드를 호출하는 방식을 지향한다.
자바 프로그래머는 객체 지향과 함수형 방식을 혼합한다. Iterator 객체를 통해 내부 상태를 포함하는 자료구조를 탐색하며 함수형 방식으로 자료구조에 들어있는 값의 합을 계산할 수 있다.
특징
함수형 프로그래밍에서 함수란 0개 이상의 인수를 가지고 한개 이상의 결과를 반환해야 하며, 이 때 다른 객체의 필드를 고치는 등 시스템의 다른 부분에 영향을 미치는 side effect를 가지면 안된다.
지역 변수만을 변경해야 한다.
함수나 메서드에서 참조하는 객체는 불변 객체(final)여야 한다.
메서드 내에서 생성한 객체의 필드를 갱신할 수는 있지만 외부에 노출되면 안된다.
예외를 일으키지 않아야 하기 때문에 Optional을 반환하는 방법을 사용할 수도 있고, 다른 컴포넌트에 영향을 미치지 않도록 지역적으로 예외를 사용하는 방법도 사용할 수 있다.
비함수형 동작을 감출 수 있는 상황에서만 side effect를 포함하는 함수를 사용해야 한다.
참조 투명성
함수를 호출할 때 어떤 인자를 주든 항상 같은 결과를 반환하면 참조 투명성을 가진 함수이다.
프로그램 이해에 큰 도움을 주며, 비싸거나 오래걸리는 연산을 캐싱하는 최적화 기능도 사용할 수 있다.
반환한 결과가 가변 객체가 아닌 불변 객체여야 참조 투명성을 가질 수 있다.
재귀와 반복
순수 함수형 프로그래밍 언어에서는 while, for 같은 반복문을 포함하지 않는다.
아래와 같이 반복문 루프 내부에서 외부의 stats 객체 상태를 변화시키는 경우 함수형과 상충하는 side effect가 발생하게 된다.
반복문을 사용하는 코드는 재귀를 사용해 구현할 수 있다.
하지만 반복문보다 재귀문을 사용하면 리소스가 많이 든다. 왜냐하면 함수를 호출할 때마다 호출 스택에 호출 정보를 저장할 스택 프레임이 쌓이기 때문이다. 이로 인해 입력 값에 비해 메모리 사용량이 증가하게 된다.
함수형 언어에서는 이를 해결하기 위해 tail-call optimization을 제공하여 컴파일러가 하나의 스택 프레임을 재활용할 수 있도록 한다.
아래는 factorial 을 구하는 로직을 재귀 방식으로 구현한 것이다.
아래는 factorial을 구하는 로직을 tail-call optimization이 가능하도록 구현한 것이다. 위 재귀 방식과 가장 큰 차이점은 이전 함수로부터 값을 받아 곱할 필요 없이 그대로 값을 반환(tail-call)하면 된다는 것이다. 이로 인해 특정 함수 정보를 담고 있는 스택 프레임을 생성할 필요가 없어 컴파일러가 tail-call optimization을 해줄 수 있다.
자바에서는 tail-call optimization을 지원하고 있지 않으므로, 반복을 스트림으로 대체하여 함수형을 유지할 수 있다.
Last updated