말은 많이 들어봤던 함수형 프로그래밍, 제대로 들여다본건 이번이 처음이다. 생각보다 오래된 개념이며, 제대로 이해한다면 도움이 될 것이 분명해 보였다. 와 근데 진짜 정말 너무 어려웠다. 내가 여지껏 경험했던 프로그래밍 개념들 중 단연 가장 어려웠던 것 같다.
함수형 프로그래밍이란, 함수 중심 프로그래밍이라고도 볼 수 있는데, 자료처리를 수학적 함수의 계산으로 표현하고, 상태와 가변 데이터 대신 불변 데이터를 프로그래밍하는 패러다임이다. 그동안은 프로그래밍에서의 함수는 수학에서의 그것과 많이 다르다고 배워왔는데, 함수 중심 프로그래밍 패러다임에서는 드디어 수학적 함수의 정의와 비슷한 함수의 정의가 된다.
여기서 말하는 "불변 데이터"가 진짜 날 미치게했다... 근데 결국엔 어떤 함수가 외부 환경으로부터 완전히 독립되었다는 개념으로 받아들였다. 그러니까, js에서 array의 map 메소드는 함수형 프로그래밍을 완전히 만족하는 함수인데, 얘는 인풋데이터를 전혀 건들지 않으며, 아웃풋 데이터도 완전히 새로운 객체이다.
다음은 드림코딩 엘리님 강의를 듣고 정리한 내용이다.
1. Pure Functions
함수에서 외부의 상태값을 참조하거나 변경하지 않는다.
동일한 인자를 주면 항상 동일한 결과를 반환한다. 외부의 영향을 받으면 안된다.
❌
let num = 1;
function add(a) {
return a + num;
}
✅
function add(a, b) {
return a + b;
}
2. Stateless, Immutabuility
함수에 전달된 인자를 변경하지 않는다. 함수 내부에서 인자를 변경하면 안된다.
전달된 인자를 변경하지 않고, 새로운 오브젝트를 만들어서 반환한다.
❌
let person = { name: "John", age: 20 };
function increaseAge(person) {
person.age++;
return person;
}
✅
let person = { name: "John", age: 20 };
function increaseAge(person) {
return { ...person, age: person.age + 1 };
}
✅
let person = Object.freeze({ name: "John", age: 20 });
function increaseAge(person) {
return Object.freeze({ ...person, age: person.age + 1 });
}
위 두가지 원칙을 지키면서 함수를 구현하면, 함수는 참조 투명성을 지키게 된다. 즉, 부작용(side effect)가 없는 함수가 된다.
멀티쓰레딩 환경에서도 안정적으로 동작할 수 있다.
3. Expression Only
if나 switch, for과 같은 여러 문장을 사용하는 것을 지양한다. 함수는 단일 표현식으로 구현한다.
❌
let numbers = [1, 2, 3];
function multiply(numbers, multiplier) {
let result = [];
for (let i = 0; i < numbers.length; i++) {
result.push(numbers[i] * multiplier);
}
return result;
}
✅
let numbers = [1, 2, 3];
function multiply(numbers, multiplier) {
return numbers.map((n) => n * multiplier);
}
4. First Class and Higher Order Functions
마지막으로 함수는
1. 다른 데이터와 마찬가지로 변수에 할당하거나, 인자로 전달하거나, 반환값으로 사용할 수 있다. (first class)
2. 함수 자체를 인자로 전달하거나, 함수를 반환하는 함수를 만들 수 있다. (higher order)
**first class**
const addTwo = (a) => a + 2;
const multiplyTwo = (a) => a * 2;
const transform = (numbers) => numbers.map(addTwo).map(multiplyTwo);
console.log(transform([1, 2, 3, 4])); // [6, 8, 10, 12]
**higher order**
const addToppings = (topping) => (food) => food + topping;
const egg = addToppings("🍳");
const bacon = addToppings("🥓");
console.log(egg("🥞")); // 🥞🍳
console.log(bacon("🥞")); // 🥞🥓