[JS] Currying과 Composition

찐새·2023년 11월 12일
0

Javascript

목록 보기
10/11
post-thumbnail
  • 함수형 프로그래밍은 함수를 1급 객체로 취급한다.
  • 1급 객체는 표현식에 할당할 수 있다.
  • JS는 함수를 1급 객체로 취급하고, 변수에 할당하여 평가할 수 있다.
  • 그러므로 JS는 함수형 프로그래밍과 잘 어울린다.

커링(Currying)컴포지션(Composition)은 함수형 프로그래밍의 핵심 개념에 속한다. 이 둘을 잘 활용하면 가독성과 재사용성이 높은 코드를 작성할 수 있다고 한다.

커링(Currying)

커링Haskell Curry에서 유래된 함수 개념으로, 여러 인자를 받는 함수를 단일 인자 함수로 변경하는 방식을 말한다. 커링 - 위키백과

세 개의 number를 받아 곱한 결과를 반환하는 함수를 예시로 들어 보자.

function multiple(a, b, c) {
  return a * b * c;
}

여기서 인자는 서로에게 영향을 끼친다. 만약 특정 상황에서 a는 2로 고정하고 bc만 바꾼다고할 때 코드 작성은 귀찮아질 것이다.

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;

컴포지션(Composition)

컴포지션은 두 개 이상의 함수를 결합해 하나의 함수를 생성하는 과정이다. 유튜브를 보다가 배운 내용으로 정리한다.

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"
}
*/

ES5 문법으로 작성하기

위 코드는 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 풀이 및 첨언

profile
프론트엔드 개발자가 되고 싶다

0개의 댓글