안녕하세요~! 비브로스 웹프론트엔드팀 한혜경입니다. 😁
오늘은 프로그래밍을 공부하는 분들이라면 한 번쯤은 들어봤을 그 단어,
“함수형 프로그래밍”에 대한 포스팅입니다.
글의 작성 배경
요즘 개발 포스팅을 읽다 보면 자주 보이는 주제입니다.
객체 지향 프로그래밍(Object-Oriented Programming, 이하 OOP)과 비교 개념으로 거론되는데요,
함수형 프로그래밍(Functional Programming, 이하 FP)에 대한 개념과 어떻게 적용할 수 있는지,
FP는 어떤 상황에서 적용하면 더 좋은지 고민하는 시간을 가지면 좋을 것 같아서
주제를 선정하게 되었습니다.
그래서 함수형 프로그래밍이 뭐야? 🤷♀️
함수형 프로그래밍은 “프로그래머가 일하는 패러다임” 중 하나입니다.
그 외의 패러다임에는 객체 지향 프로그래밍, 절차지향 프로그래밍, 함수형 반응형 프로그래밍 등이 있습니다.
(이렇게 시리즈가 된다)
패러다임(Paradigm)이란? 한 시대 사람들의 견해나 사고를 근본적으로 규정하고 있는 인식의 체계. 또는, 사물에 대한 이론적인 틀이나 체계.
출처: Oxford Languages
함수형 프로그래밍이 최근 새로 나온 패러다임은 아닙니다.
Programming language의 역사
출처: 알렌 터커의 “프로그래밍 언어”
첫 번째 OOP 언어인 Simula보다도 먼저 나왔죠.
(저도 최근 몇 년 사이에 자주 들어서 얼마 전에 나온 개념이라 생각했어요)
그런데 왜 이제야 트랜드가 된 거야? 🤷♀️
사람의 사고방식과 더 가까운 건 OOP로,
현실 세계를 프로그래밍 설계에 반영한 프로그래밍 방식입니다.
그와 비교해서 FP는 수학적 함수의 개념을 프로그래밍 설계에 반영한 프로그래밍 방식이죠.
함수: 두 변수 x, y에 대하여 x가 정해지면 그에 따라 y의 값이 하나만 결정될 때, y를 x의 함수라 한다. y = f(x)
FP에서의 함수: 함수의 파라미터가 정해지면 그에 따라 결과값은 항상 같은 값을 갖는다.
출처: 위키백과
FP보다 러닝 커브가 낮아 학교 등에서 가르칠 때도 OOP를 위주로 가르치고
접할 수 있는 정보도 OOP가 더 많았습니다.
그러다 보니 OOP가 더 보편적으로 쓰이고 있었죠.
최근 FP가 핫해진 이유로는 빅데이터와 같이 방대한 양의 데이터를 다루는 일이 많아졌고,
복잡한 서비스가 계속 나오면서 안정적으로 프로그램을 짜는 것의 중요성이 높아지고
부수 작용이 적은 FP 방식이 다시 주목받은 게 아닐까 싶습니다.
아무튼, 다시 개념으로 돌아갑시다~!
FP는 다음과 같은 특징을 가집니다.
- 순수함수
- 같은 Input에 대해 언제나 동일한 Output을 가져야 합니다.
- Input에 대해 Output이 예측할 수 있어야 합니다.
function add(num1: number, num2: number): number {
return num1 + num2;
}
add(2, 3); // result: 5
- 불변성
- 외부 변수를 사용하더라도, 원본에 접근해서 변형하는 게 아니고, 인자로 받은 후 사본으로 작업합니다.
- 외부 변수에 직접 접근하지 않기 때문에 부수 작용(Side effect) 위험성이 낮습니다.
const person = { name: '한혜경', age: 33 };
// 원본 직접 변경
function mutableIncreaseAge() {
person.age = person.age + 1;
return person;
}
// 사본 변경
function immutableIncreaseAge(person) {
return { ...person, age: person.age + 1 };
}
const mutableResult = mutableIncreaseAge();
const immutableResult = immutableIncreaseAge(person);
person === mutableResult; // true;
person === immutableResult; // false;
예측 불가능한 부수 작용의 예
- 높은 수준의 추상화
- 선언적 패턴
- JS에서 제공해주는 메소드를 사용할 때 Input에 대한 Output을 알아야 할 뿐, 내부에서 어떻게 실행되는지 알아야 할 필요는 없습니다.
- 무엇을 해결할지에 집중하고 어떻게 해결하는지는 컴퓨터에 위임합니다.
const arr = [1, 2, 3];
// Q. 배열을 순회하여 짝수만 남겨주세요.
// 명령형 - 과정이 중요
const result = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] % 2 === 0) {
result.push(arr[i]);
}
}
// 선언형 - 결과가 중요
const result = arr.filter(num => num % 2);
- 커링
- 모든 인자가 준비되지 않았을 때 부분 적용된 상태의 함수를 만들어내서 마련해두거나(like. 클로져) 다른 함수에 인자로 넘겨주는 등 로직을 짜는 데 있어 더 안전하고 코드의 양도 줄일 수 있습니다.
// js
const arr = [1, 2, 3];
/* curring을 적용하지 않았을 때 */
const sum = (num1, num2) => {
return num1 + num2;
}
const sumResult = arr.map(num => sum(num, 5));
console.log(sumResult); // [6, 7, 8]
/* curring을 적용했을 때 1 */
const add = (num1) => (num2) => {
return num1 + num2;
};
const addResult = arr.map(add(5));
console.log(addResult); // [6, 7, 8]
/* curring을 적용했을 때 2 */
const add10 = add(10);
const add10Result = arr.map(add10);
console.log(add10Result); // [11, 12, 13]
// lodash
import _ from 'lodash/fp';
const curried = _.curry((num1, num2) => num1 + num2);
const curriedResult = arr.map(curried(5));
console.log(curriedResult); // [6, 7, 8]
왜 함수형 프로그래밍을 배워야 해? 👩🏫
- 함수형 프로그래밍은 함수를 합성해서 복잡한 프로그램을 쉽게 만들 수 있습니다.
- 즉, 복잡한 프로그램을 안전하고 작게 나눌 수 있습니다.
input(블록 모양) → output(맞는 틀)을 구현했다면 안전하게 구현할 수 있었을 텐데…
- 재사용할 수 있는 코드가 많아집니다.
- 약간의 차이 때문에 재사용 못 하고 복사해 붙여 넣는 경우가
왕왕있습니다. 그 차이에 대한 값을 FP 형태로 사용하면 결과를 예측할 수 있어 덜 복잡하게 느껴집니다.
- 약간의 차이 때문에 재사용 못 하고 복사해 붙여 넣는 경우가
- JavaScript는 OOP도 FP도 지원하는 언어입니다.
💡 JavaScript를 창시한 Brendan Erich는 언어를 개발할 당시 유행하던 객체지향에 한계를 느끼고
LISP, scheme 등 함수형 프로그래밍에 관심이 있었기에 "함수형 프로그래밍의 형태"로 언어를 만들고 싶어 했습니다.
하지만 Netscape의 그의 상사는 당시 개발자들이 제일 많이 쓰던 "Java와 같은 문법"
으로 만들기를 요구했기에 결국 둘의 혼종의 형태로 세상에 나오게 되었습니다. :)
JavaScript는 자유도가 높은 언어인 만큼 복잡한 기능을 좋게도, 나쁘게도 구현할 수 있습니다.
기능 구현에 있어서 더 효율이 좋은 패러다임을 적용할 수 있어야 합니다.
OOP vs FP Which is Winner!? 🥊
OOP vs FP 과연 승자는…!?
바로 함수형 프로그래밍!!!!!
.
.
.
이 아니고…
모든 프로젝트를 100% FP에 맞춰 작업하기도 OOP에 맞춰 작업하기도 어렵습니다.
중요한 것은 “어떤 작업에 대해 OOP가 잘 맞을 것인지, FP가 잘 맞을 것인지“ 판단 후 좋은 점을 이용해야 한다는 것입니다.
어떻게 적용할까? 🤦♀️
RxJS
- 외부 변수를 선언하여 사용하지 말고, operators를 이용하여 값을 정제합니다.
import { fromEvent } from 'rxjs';
import { pluck, filter, map } from 'rxjs/operators';
const keypress$ = fromEvent(document, 'keydown').pipe(
pluck('key'),
map(key => key.toUpperCase()),
take(1)
);
keypress$.subscribe(pressedKey => {
console.log(pressedKey);
});
React with Typescript
- Custom hooks을 이용하여 재사용 가능한 코드를 작성합니다.
import { useState, useEffect } from "react";
function App() {
const happyPress = useKeyPress("h");
const sadPress = useKeyPress("s");
const robotPress = useKeyPress("r");
const foxPress = useKeyPress("f");
return (
<div>
<div>h, s, r, f</div>
<div>
{happyPress && "😊"}
{sadPress && "😢"}
{robotPress && "🤖"}
{foxPress && "🦊"}
</div>
</div>
);
}
function useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState<boolean>(false);
function downHandler({ key }) {
if (key === targetKey) {
setKeyPressed(true);
}
}
const upHandler = ({ key }) => {
if (key === targetKey) {
setKeyPressed(false);
}
};
useEffect(() => {
window.addEventListener("keydown", downHandler);
window.addEventListener("keyup", upHandler);
return () => {
window.removeEventListener("keydown", downHandler);
window.removeEventListener("keyup", upHandler);
};
}, []);
return keyPressed;
}
/*
참고: https://usehooks.com
위 사이트에서 유용한 custom hooks를 제공하고 있습니다.
Custom hooks 정의에 어려움을 느끼신다면 도움이 많이 될거에요!
*/
회고 👩💻
일단 계속 미루고 있던 세미나를 진행하게 되어 홀가분하고 (최고로 기쁨 🥹)
함수형 프로그래밍을 공부하면서 제가 쓰는 방식이 함수형 프로그래밍에 맞는 것인가 한 번 더 고민하게 되는 시간이 있어 좋았습니다.
마지막으로, 제가 처음 개념을 접했을 때 생각했던 것처럼 객체 지향 프로그래밍과 함수형 프로그래밍을 반드시 한 가지만 선택해서 사용해야 하는 건 아니라는 점!
필요한 때 더 적절한 패러다임을 섞어 사용하면 더 좋다는 것을 공유해 드리고 싶었습니다.
다음 포스팅은 객체지향 프로그래밍을 다뤄보고 싶군요 🤩
참고
- 함수형프로그래밍이 대세다?! (함수형 vs 객체지향)
- 함수형 프로그래밍이 뭔가요?
- https://evan-moon.github.io/2019/12/15/about-functional-thinking/#함수형-프로그래밍을-왜-알아야하나요
- https://velog.io/@teo/functional-programming
💡 🚀 우리팀이 일하는 방법! 👉🏻 비브로스 웹프론트엔드팀 👈🏻