- 함수형 프로그래밍은 함수를 1급 객체로 취급한다.
- 1급 객체는 표현식에 할당할 수 있다.
JS
는 함수를 1급 객체로 취급하고, 변수에 할당하여 평가할 수 있다.- 그러므로
JS
는 함수형 프로그래밍과 잘 어울린다.
커링(Currying
)과 컴포지션(Composition
)은 함수형 프로그래밍의 핵심 개념에 속한다. 이 둘을 잘 활용하면 가독성과 재사용성이 높은 코드를 작성할 수 있다고 한다.
커링은 Haskell Curry에서 유래된 함수 개념으로, 여러 인자를 받는 함수를 단일 인자 함수로 변경하는 방식을 말한다. 커링 - 위키백과
세 개의 number를 받아 곱한 결과를 반환하는 함수를 예시로 들어 보자.
function multiple(a, b, c) {
return a * b * c;
}
여기서 인자는 서로에게 영향을 끼친다. 만약 특정 상황에서 a
는 2로 고정하고 b
와 c
만 바꾼다고할 때 코드 작성은 귀찮아질 것이다.
const multiA = multiple(2, 2, 2);
const multiB = multiple(2, 3, 3);
const multiC = multiple(2, 3, 4);
multiple
을 커링으로 작성하면 코드가 심플해진다.
function multiple(a) {
return function (b) {
return function (c) {
return a * b * c;
}
}
}
const fixTwo = multiple(2);
const twoTwo = fixTwo(2)(2);
const threeThree = fixTwo(3)(3);
const threeFour = fixTwo(3)(4);
재사용성도 높아진다.
// a는 2로 고정
const fixTwo = multiple(2);
// b를 3으로 고정
const addFixThree = fixTwo(3);
let result = addFixThree(4); // 24
result = addFixThree(5); // 30
// b를 4로 고정
const addFixFour = fixTwo(4);
result = addFixFour(4); // 32
result = addFixFour(5); // 40
ES6
의 화살표 함수로 표현하면 다음과 같다.
const multiple = (a) => (b) => (c) => a * b * c;
컴포지션은 두 개 이상의 함수를 결합해 하나의 함수를 생성하는 과정이다. 유튜브를 보다가 배운 내용으로 정리한다.
const user = {
id: 1,
firstName: "Real",
lastName: "Bird"
}
genFullName(user);
genAddress(user);
removeNames(user);
result = {
id: 1,
fullName: "Real Bird",
address: "Republic of Korea"
}
user
객체가 있고 각각의 함수를 거치면 result
객체가 되어야 한다. 함수를 하나씩 실행하거나 하나의 함수에서 처리할 수도 있지만, 그런 방식은 명령형에 가깝다. 커링을 이용한 컴포지션 함수를 만들면 선언형으로 처리할 수 있다.
const compose = (...fns) => (obj) => fns.reduce((c, fn) => fn(c), obj);
ES6+
의 스프레드 문법으로 여러 개의 인자를 받는다. 그 후 커링으로 객체를 받고, reduce
함수를 이용해 이전 함수 실행 결과를 다음 함수의 인자로 주입한다.
const genFullName = (user) => {
return {
...user,
fullName:`${user.firstName} ${user.lastName}`
}
}
const genAddress = (user) => {
return {
...user,
address: "Republic of Korea"
}
}
const removeNames = (user) => {
delete user.firstName;
delete user.lastName;
return user;
}
const result = compose(genFullName, genAddress, removeNames);
console.log(result(user));
/*
user {
id: 1,
fullName: "Real Bird",
address: "Republic of Korea"
}
*/
위 코드는 ES6
문법을 주로 사용하여 작성했다. 참고한 영상에서 해당 코드를 ES5
문법으로 작성해 보라고 하여 한 번 작성해 봤다.
var user = {
id: 1,
firstName: "Real",
lastName: "Bird"
}
function compose() {
var fns = arguments;
return function (obj) {
var result;
for (var i = 0; i < fns.length; i = i + 1){
result = fns[i](obj);
}
return result;
}
}
function genFullName(user) {
user.fullName = user.firstName + " " + user.lastName;
return user;
}
function genAddress(user) {
user.address = "Republic of Korea";
return user;
}
function removeNames(user) {
delete user.firstName;
delete user.lastName;
return user;
}
var result = compose(genFullName, genAddress, removeNames);
console.log(result(user));
ES6
문법을 싹 걷어냈다.
const
대신 var
, 화살표 함수(()=>{}
) 대신 function
을 사용했다.
스프레드(...fns
) 문법도 사용할 수 없기 때문에 compose
에서 받는 인자들은 arguments
를 이용했다. 객체는 몽키 패치로 새 속성을 추가했다.
reduce
는 es5 빌트인 메서드라 사용해도 괜찮지만, reduce
를 사용하지 않는 조건이 있다고 쳤다. 나는 반복문을 돌려 fns
의 함수들을 실행했다.
영상에서는 재귀를 이용해 compose
를 구현하더라.
function compose() {
var fns = arguments;
return function rfn(obj, i) {
i = i || 0;
if (i < fns.length) {
return rfn(fns[i](obj), i + 1);
}
return obj;
}
}
커링과 컴포지션에 대해 듣기만 했을 뿐, 무엇인지 전혀 알지 못했다. 우연한 계기로 유튜브 영상을 보고 아주 유용하고 중요한 개념 하나를 배웠다. 익숙해지도록 노력해야지.
참고
@시코 - React 이론 4강 - 함수형 프로그래밍의 백미, Currying과 Composition
@시코 - React 이론 5강 - Currying, composition 풀이 및 첨언