[프로그래밍 언어론] 함수형 프로그래밍
등장 배경
명령형 언어는 코드의 명령을 순차적으로 처리하는 방식으로, 이는 명령어와 데이터를 같은 메모리에 저장하고 순차적으로 실행하는 ‘폰 노이만 구조’에 기반한다. 이러한 구조 덕분에 명령형 언어들은 유사한 형태를 띄며, 하드웨어와 밀접하게 동작할 수 있다.
하지만 언어는 점점 더 커지고 복잡해지지만 기능은 크게 향상되지 않았다. 폰 노이만 구조의 문제점은 아래와 같다.
- 폰 노이만 병목 : 폰 노이만 구조의 컴퓨터는 CPU와 메모리 사이의 데이터 전송 병목을 가지여, 이는 언어 설계에도 영향을 미쳐 워드 단위의 프로그래밍을 강요한다.
- 상태 기반 프로그래밍 : 프로그램의 의미가 상태 변화에 밀접하게 연결되어 있어, 프로그램의 각 부분이 전체 상태에 미치는 영향을 추적하기 어렵다.
- 어떤 전역 변수 x가 있다면 x의 값은 상태에 따라 달라진다. 누가 언제 x를 바꿨는지는 파악하기 어렵다. 특히 비동기 상태에서 의도하지 않은 값이 들어갈 수 있다.
- 표현력 부족 : 기존 언어의 구성 요소로는 강력한 조합 형태를 효과적으로 사용할 수 없어, 새로운 프로그램을 기존 프로그램으로부터 구축하는 것이 어려움
- 기존 프로그램 조각을 단순히 "조합" 해서 새로운 프로그램을 만들기가 어렵다.
1977년 ACM 튜링상 수상자인 배커스(John Backus)는 이러한 문제점을 지적하고, 함수형 프로그래밍과 응용 상태 전이 시스템을 대안으로 제시했다.
함수형 프로그래밍?
자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임의 하나
수학의 함수처럼 입력 -> 출력만 보고 작동하도록 만드는 방식
상태 변화나 부작용을 최대한 피하는 것이 핵심이다.
즉, 같은 값을 입력하면 같은 값이 나온다.
당연한거 아니야?
명령형 / 객체지향에서는 함수나 메서드가 외부 상태나 객체 내부 상태에 의존하는 경우가 많다.
int counter = 0;
void increment(void) {
counter++;
}
int main(void) {
increment(); // 1
increment(); // 2
}
위 코드를 보면 main문에서 1과 2가 같은 increment() 함수이지만 결과가 다르다.
counter 변수의 값, 즉 "상태"에 의존하고 있는것!
위 코드를 함수형 스타일로 바꾸면
int increment(int x) {
return x + 1;
}
int main(void) {
const int counter = 0;
const int counter1 = increment(counter); // 1
const int counter2 = increment(counter); // 2
}
increment() 함수는 항상 인자에 1을 더해서 리턴한다. 즉, 전역 변수나, 객체의 "상태"를 바꾸지 않고 항상 새로운 값을 리턴한다.
이렇게 항상 새로운 값을 리턴해주는 "순수 함수"를 만들면 상태를 변경하지 않고도 (const) 같은 동작을 하게 할 수 있다.
함수형 프로그래밍은 다음과 같은 개념들을 기반으로 구성된다.
- 순수 함수
- 불변성
- 고차함수
1. 순수 함수
오직 함수의 입력만이 함수의 결과에 영향을 주는 함수
순수함수는 다음과 같은 특성을 가진다.
- 함수는 결과를 생성하기 위해 매개변수와 인수만 사용한다.
- 외부 변수, 전역 상태를 참조하지 않는다.
- 함수는 부작용(side effect)이 없다.
- 함수 호출 전후로 프로그램 외부 상태가 절대 변하지 않는다.
- 함수는 참조 투명성(referential transparency)을 가진다.
- 어떤 식 e가 있을 때, 모든 프로그램 p에 대해 p안의 e를 e를 평가한 결과로 치환해도 p의 의미에 영향을 끼치지 않으면 e는 참조 투명하다
const rst = square(3) + square(3);
// square(3)의 결과는 항상 9다. -> 9 + 9로 치환해도 결과 같음
// e = square(3)
// e를 평가한 결과 : 9
2. 불변성
값이나 상태를 변경하지 않고, 새로운 값을 생성
counter 예제에서의 const에 해당한다.
함수형 프로그래밍에서는 기존 값을 절대 변경하지 않고, 필요한 경우 복사 후 수정한 새 값을 반환한다.
이로 인해 프로그램의 상태 추적이 쉬워지고, 사이드 이펙트를 줄일 수 있다.
3. 고차 함수
함수를 인자로 받거나 함수를 반환하는 함수
고차 함수의 예시로 리액트의 useEffect가 있다.
고차 함수는 다른 함수를 조작 가능한 데이터처럼 다루는 함수이다.
이 덕분에 함수 조합, 동작 캡슐화, 반복 추상화가 가능해진다.
useEffect(() => {
console.log("Hello world!");
}, []);
// 함수를 인자로 받고, 내부적으로 실행 시점을 제어한다.
함수형 프로그래밍 시스템
- 기존 함수들을 결합하여 새로운 함수를 만드는 조합 형태를 중심으로 한다.
const double = x => x * 2;
const square = x => x * x;
const doubleThenSquare = x => square(double(x));
console.log(doubleThenSquare(3)); // (3 * 2)^2 = 36
- 변수를 사용하지 않고, 함수를 직접적으로 정의하고 조합한다.
// 안 좋은 (명령형) 방식
const temp = double(3);
const result = square(temp);
// 좋은 (함수형) 방식
const result = square(double(3));
- 프로그램 변환 및 방정식 풀이에 유용한 대수적 속성을 가지며, 이를 통해 프로그램의 동작을 추론하고 검증한다.
// 함수 정의
const addOne = x => x + 1;
// 이 표현은
const y = addOne(5) + addOne(5);
// 이렇게 바꿔도 똑같음 (참조 투명성)
const y = 6 + 6;
- 더 간단한 함수와 조합 형태로부터 계층적으로 구성한다.
// 기본 함수
const isEven = x => x % 2 === 0;
const double = x => x * 2;
// 더 높은 수준의 함수
const doubleIfEven = x => isEven(x) ? double(x) : x;
대표적인 함수형 시스템에는
- Haskell
- Lisp
- JavaScript가 있다.
참고
https://www.zerocho.com/category/JavaScript/post/576cafb45eb04d4c1aa35078
(JavaScript) 함수형 프로그래밍(Functional Programming)
안녕하세요. 이번 시간에는 함수형 프로그래밍에 대해서 알아보겠습니다. 사실 함수형 프로그래밍을 중급 강좌에서 다루기엔 좀 어려운 감이 있습니다만, 다음 시간의 실습을 위해 다루도록 하
www.zerocho.com
https://nybot-house.tistory.com/69
1. C++ 함수형 프로그래밍 소개
C++에서 함수형 프로그래밍을 지원하기 시작한 배경 C++ 언어의 스타일은 OOP, 즉 객체 중심 언어이다. 그런데 시간이 지나며 새로운 언어들이 생기고, 그런 새 언어들이 C++보다 더 쉽게 높은 언어
nybot-house.tistory.com
https://mangkyu.tistory.com/111
[프로그래밍] 함수형 프로그래밍(Functional Programming) 이란?
1. 함수형 프로그래밍(Functional Programming)에 대한 이해 [ 프로그래밍 패러다임(Programming Paradigm) ] 프로그래밍 패러다임(Programming Paradigm)은 프로그래머에게 프로그래밍의 관점을 갖게 하고 코드를
mangkyu.tistory.com