이미지 출처: modernescpp.com
함수형 프로그래밍은 선언형 프로그래밍의 한 종류로, 특정한 원칙과 개념에 중점을 둔 프로그래밍 스타일이다. 함수형 프로그래밍은 순수 함수(Pure Function), 함수의 일급 시민성(First-class Functions), 불변성(Immutability), 고차 함수(Higher-order Functions) 등의 특징을 가지고 있다.
결국, 함수형 프로그래밍은 선언형 프로그래밍의 철학을 따르면서, 상태 변화를 최소화하고, 순수 함수로 문제를 해결하는 방법론이다. 이 방법론은 코드가 더욱 간결하고 예측 가능하며, 유지보수가 용이하게 만들어준다.
즉, 함수형 프로그래밍은 어떻게 프로그래밍할 것인가에 대한 하나의 접근 방식으로, 선언형 프로그래밍의 철학을 구체화한 스타일이라고 볼 수 있다. 다양한 프로그래밍 언어에서 적용될 수 있으며, 함수형 프로그래밍을 사용하면 코드가 더욱 명확하고 안전해진다.
함수형 프로그래밍은 수학적 함수의 개념을 프로그래밍에 적용하기 위해 탄생한 프로그래밍 패러다임이다. 이 개념은 초기 Lisp 언어에서 시작되었으며, 이후 Haskell, ML, Erlang과 같은 언어들이 함수형 프로그래밍의 철학을 발전시켰다. 함수형 프로그래밍은 상태 변화와 부작용을 최소화하여 코드를 더욱 예측 가능하고 안정적으로 만들려는 목적에서 출발했다.
초기에는 주로 학계에서 연구되었으나, 최근에는 병렬 처리와 분산 시스템의 중요성이 부각되면서 실무에서도 점점 더 널리 사용되고 있다. 함수형 프로그래밍은 이러한 환경에서 안정적이고 효율적인 코드를 작성하는 데 강력한 도구로 자리잡고 있다.
순수 함수는 동일한 입력에 대해 항상 동일한 출력을 반환하며, 외부 상태에 영향을 미치지 않고, 외부 상태로부터도 영향을 받지 않는 함수를 말한다. 이는 함수형 프로그래밍의 기본 원리로, 부작용(Side Effect)을 없애고 코드를 더욱 예측 가능하게 만든다.
생각해보자. 커피를 만드는 기계를 가지고 있다고 가정하자. 이 기계는 같은 종류의 원두를 넣을 때마다 항상 같은 맛의 커피를 만들어 낸다. 여기서 중요한 점은, 이 기계가 커피를 만드는 과정에서 외부 환경에 영향을 받지 않고, 결과적으로 만들어진 커피가 외부의 상태에 영향을 미치지 않는다는 것이다.
이 기계는 항상 같은 입력(원두)을 받아 같은 출력을 제공(커피)하며, 외부의 어떤 것도 변경하지 않는다. 이는 순수 함수의 개념과 비슷하다.
이제 코딩에서 순수 함수가 어떻게 작동하는지 보자. 예를 들어, 두 수를 더하는 간단한 순수 함수를 작성해보겠다.
// 순수 함수 예시
function add(x, y) {
return x + y;
}
console.log(add(2, 3)); // 출력: 5
console.log(add(2, 3)); // 출력: 5 (항상 동일한 결과)
위의 add
함수는 순수 함수이다. 이 함수는 두 입력 x
와 y
를 받아 그들의 합을 반환한다. 이 함수의 특징은 동일한 입력값에 대해 항상 동일한 결과를 반환하며, 함수 외부의 상태에 영향을 미치지 않는다는 점이다.
이제 반대로, 부작용이 있는 코드를 살펴보자. 아래 예시는 외부 상태를 변경하는 함수이다.
let total = 0;
function addToTotal(x) {
total += x;
}
addToTotal(5);
console.log(total); // 출력: 5
addToTotal(10);
console.log(total); // 출력: 15 (total 값이 변경됨)
addToTotal
함수는 외부 변수 total
을 변경한다. 이 함수는 부작용(Side Effect)이 있는 함수로, 외부 상태(total
)를 변경하므로 순수 함수가 아니다. 이렇게 외부 상태를 변경하는 함수는 예측하기 어렵고, 코드의 유지보수가 복잡해진다.
참조 투명성(Referential Transparency)은 순수 함수의 또 다른 중요한 개념이다. 이는 같은 입력이 주어졌을 때 항상 동일한 출력을 제공한다는 의미이다. 즉, 함수 호출을 그 결과값으로 대체해도 프로그램의 동작에 아무런 영향을 미치지 않아야 한다.
console.log(add(2, 3)); // 출력: 5
console.log(5); // 출력: 5 (위 함수 호출을 5로 대체해도 동일한 결과)
위 코드에서 add(2, 3)
를 5
로 대체해도 프로그램의 결과는 변하지 않는다. 이것이 참조 투명성의 예시이다.
함수의 일급 시민성(First-class Citizen)은 함수형 프로그래밍의 핵심 개념으로, 함수가 변수에 할당되거나, 인자로 전달되거나, 다른 함수에서 반환될 수 있는 능력을 의미한다. 이를 통해 고차 함수와 같은 강력한 기능을 구현할 수 있으며, 코드의 유연성과 재사용성을 높일 수 있다.
함수형 프로그래밍에서 고차 함수는 다른 함수를 인자로 받거나, 함수를 반환하는 함수를 의미한다. 이는 함수형 프로그래밍의 중요한 특성으로, 함수를 조합하거나 변형하는 데 사용된다.
실생활에서 어떤 개념이 일급 시민으로 취급된다는 것은 우리가 그것을 자유롭게 다룰 수 있다는 것을 의미한다. 예를 들어, 책을 생각해보자. 책은 다음과 같은 다양한 방식으로 다뤄질 수 있다.
이와 마찬가지로, 함수가 일급 시민으로 취급된다는 것은 우리가 함수를 다른 데이터 타입처럼 자유롭게 다룰 수 있다는 의미이다.
이제 자바스크립트에서 함수의 일급 시민성을 보여주는 몇 가지 예시를 보겠다.
const greet = function(name) {
return `Hello, ${name}!`;
};
console.log(greet("Alice")); // 출력: Hello, Alice!
여기서 greet
라는 변수에 함수를 할당했다. 이 변수는 이제 함수처럼 동작하며, 나중에 호출할 수 있다.
function logResult(fn, value) {
console.log(fn(value));
}
logResult(greet, "Bob"); // 출력: Hello, Bob!
logResult
함수는 다른 함수(fn
)를 인자로 받아, 그 함수를 실행하고 결과를 출력한다. 이 예시에서 greet
함수를 logResult
의 인자로 전달하여, greet
함수가 실행되도록 했다.
function createMultiplier(multiplier) {
return function(value) {
return value * multiplier;
};
}
const double = createMultiplier(2);
console.log(double(5)); // 출력: 10
이 예시에서 createMultiplier
함수는 함수를 반환한다. 이 반환된 함수는 특정 값을 인자로 받아 그 값을 multiplier
로 곱한 결과를 반환한다. 여기서 double
은 createMultiplier(2)
호출의 결과로 생성된 함수이며, double(5)
는 5에 2를 곱한 10을 반환한다.
고차 함수는 함수형 프로그래밍에서 중요한 개념으로, 함수를 인자로 받거나 함수를 반환하는 함수를 의미한다.
function applyOperation(operation, x, y) {
return operation(x, y);
}
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
console.log(applyOperation(add, 3, 4)); // 출력: 7
console.log(applyOperation(multiply, 3, 4)); // 출력: 12
이 예시에서 applyOperation
은 고차 함수이다. 이 함수는 다른 함수(operation
)를 인자로 받아 그 함수를 실행한다. applyOperation(add, 3, 4)
는 add(3, 4)
의 결과를 반환하고, applyOperation(multiply, 3, 4)
는 multiply(3, 4)
의 결과를 반환한다.
불변성은 함수형 프로그래밍의 핵심 원리 중 하나로, 데이터가 한 번 생성되면 변경되지 않는 성질을 말한다. 데이터의 불변성을 유지하면 상태 변화로 인한 버그를 방지하고, 프로그램의 예측 가능성을 높일 수 있다.
불변 데이터 구조: 함수형 프로그래밍에서는 데이터 구조가 불변성을 가지며, 이는 데이터의 복사와 수정이 더 안전하고 예측 가능하게 이루어질 수 있음을 의미한다.
실생활 예시
계약서를 작성했다고 가정하자. 이 계약서의 내용을 수정해야 할 경우, 원본 계약서를 직접 수정하는 대신, 원본을 그대로 보관하고 복사본을 만들어 그 복사본에서 수정 작업을 한다고 생각해보자. 이렇게 하면 원본 계약서는 언제나 그대로 유지되며, 원본이 손상되거나 의도치 않게 변경될 위험이 없다. 이것이 바로 불변성의 개념이다.
let person = { name: "Alice", age: 25 };
person.age = 26; // 원본 객체가 직접 변경됨
console.log(person); // 출력: { name: "Alice", age: 26 }
이 코드에서는 person
객체의 age
속성을 직접 변경했다. 이로 인해 원본 객체가 변경되며, 다른 곳에서 이 객체를 참조할 때 예측하지 못한 문제가 발생할 수 있다.
let person = { name: "Alice", age: 25 };
// 새로운 객체를 생성하여 변경 사항 반영
let updatedPerson = { ...person, age: 26 };
console.log(person); // 출력: { name: "Alice", age: 25 }
console.log(updatedPerson); // 출력: { name: "Alice", age: 26 }
이 코드에서는 person
객체를 변경하지 않고, 대신 복사본을 만들어 age
속성을 수정한 후, updatedPerson
이라는 새로운 객체를 생성했다. 이 방법을 통해 원본 객체의 불변성을 유지하면서 필요한 변경 사항을 적용할 수 있다.
함수형 프로그래밍에서는 배열이나 객체 같은 데이터 구조가 불변성을 가지도록 관리된다.
const numbers = [1, 2, 3];
// 기존 배열을 변경하지 않고, 새로운 배열을 생성
const newNumbers = [...numbers, 4];
console.log(numbers); // 출력: [1, 2, 3]
console.log(newNumbers); // 출력: [1, 2, 3, 4]
이 예시에서 numbers
배열은 불변성을 유지하며, 새로운 newNumbers
배열이 생성된다. 이로 인해 원본 배열을 변경하지 않고도 데이터를 추가할 수 있다.
지연 평가는 필요할 때까지 계산을 미루는 기법으로, 불필요한 계산을 피하고 성능을 최적화하는 데 사용된다. 이는 특히 대용량 데이터 처리에서 유리하다.
함수형 프로그래밍에서는 작은 함수들을 조합하여 복잡한 동작을 구현할 수 있다. 이는 코드의 재사용성을 높이고, 모듈화된 코드를 작성하는 데 유용하다.
순수 함수형 프로그래밍 언어로, 순수 함수와 불변성의 개념을 철저히 따른다. Haskell은 함수형 프로그래밍의 모든 핵심 개념을 깊이 있게 탐구할 수 있는 언어이다.
함수형 프로그래밍의 철학을 따르는 대표적인 언어 중 하나로, 함수의 일급 시민성을 강하게 지원한다. Lisp은 또한 매크로 시스템을 통해 고도의 확장성을 제공한다.
함수형과 객체 지향 프로그래밍을 모두 지원하는 하이브리드 언어로, 함수형 프로그래밍의 개념을 적극적으로 채택했다. Scala는 JVM(Java Virtual Machine)에서 실행되며, Java와의 호환성을 제공한다.
동시성 처리를 위해 설계된 함수형 프로그래밍 언어로, Telecom 시스템에서 널리 사용된다. Erlang은 메시지 기반의 동시성 모델을 제공하며, 고가용성 시스템에 적합하다.
.NET 플랫폼을 위한 함수형 프로그래밍 언어로, F#은 함수형 프로그래밍의 강력한 특성과 객체 지향 프로그래밍의 유연성을 결합했다.
함수형 프로그래밍의 철학은 수학적 함수를 기반으로 한다. 이는 프로그램을 상태 변화와 부작용 없이 구성할 수 있게 하며, 이를 통해 코드의 신뢰성과 안정성을 높인다. 함수형 프로그래밍에서는 프로그램이 상태 변화 없이 순수 함수들로 구성되며, 이로 인해 프로그램의 동작을 예측하고 이해하기 쉽다. 이러한 철학은 프로그램의 복잡성을 줄이고, 보다 안정적이고 유지보수가 쉬운 소프트웨어를 개발하는 데 기여한다.
그래서 함수형 프로그래밍은 선언형 프로그래밍의 한 종류지만, 그 자체로는 더 구체적이고 특정한 방법을 의미한다. "무엇을" 할지 설명하는 것(선언형 프로그래밍)과, 그 설명을 함수로 풀어내는 것(함수형 프로그래밍)이 핵심이다.