[JavaScript] - 커링에 대해 알아보자

Lee Jeong Min·2021년 10월 3일
15
post-thumbnail

함수형 프로그래밍 언어와 관련된 커링에 관한 글입니다.

글을 쓰게 된 이유

최근 JavaScript에 대해서 깊게 공부하면서 기존에는 몰랐던 개념들에 대해서 많이 알게되었습니다. 특히 프로그래밍 언어가 가지는 프로그래밍 패러다임 중, 각 언어만이 가지는 고유한 프로그래밍 패러다임이 있지만 JS의 경우 클래스를 이용한 객체지향뿐만 아니라, 프로토타입 기반의 객체지향, 나아가 최근 인기있는 함수형 프로그래밍까지 가능한 멀티 패러다임 언어임을 알게되었습니다.

그래서 JavaScript에서는 어떻게 함수형 프로그래밍을 사용하는 지 궁금해졌고, 이에 대해서 찾아보다가 커링에 대해서 알게되어 이를 정리하고자 글을 작성하게 되었습니다.

커링이란

위키백과에서 커링이란 다음과 같이 정의하고 있습니다.

수학과 컴퓨터 과학에서 커링(currying)이란 다중 인수 (혹은 여러 인수의 튜플)을 갖는 함수를 단일 인수를 갖는 함수들의 함수열로 바꾸는 것을 말한다.

이 커링은 오직 JS에만 존재하는 것은 아니고 다른 언어에도 존재합니다.

그렇다면 이러한 커링을 왜 하는 것일까요? 이는 함수형 프로그래밍을 사용하는 이유와 일치합니다. 바로 부수효과를 최대한으로 줄이고, 동일한 입력이 들어가면 동일한 출력이 나오게 하여 가독성과 유지보수를 용이하게 하기 위해서 입니다.

JavaScript의 커링

실제로 JS에서 커링은 어떤식으로 이루어지는 지 알고싶어서 간단한 예제 몇가지를 가져와보았습니다.

일반적인 커링

// 커링 변환을 하는 curry(f) 함수 (일반함수 ver)
function curry(f) {
  return function(a) {
    return function(b) {
      return f(a, b);
    };
  };
}

// 커링 변환을 하는 curry(f) 함수 (화살표함수 ver)
const curry = f => a => b => f(a, b);

// f에 전달된 함수
const sum = (a, b) => a + b;

const curriedSum = curry(sum);

console.log(curriedSum(1)(2)); // 3

위 함수는 다음의 순서로 동작 됩니다.

  • curry(func)의 반환 값은 function(a) 형태입니다.
  • curriedSum(1) 과 같은 함수가 호출되었을 때, 1은 렉시컬 환경에 저장이 되고 function(b)가 반환됩니다.
  • 반환된 function(b) 함수가 2를 인수로 호출됩니다. 반환 값이 원래의 sum으로 넘겨져서 호출됩니다.
  • 최종적으로 sum(1, 2) 가 호출되어 1 + 2인 3이 반환됩니다.

lodash를 통한 커링

lodash를 사용하면 좀 더 간단하게 커링을 구현할 수 있습니다.

const _ = require('lodash');

const sum = (a, b) => a + b;

const curriedSum = _.curry(sum);

console.log(curriedSum(1)(2));

lodash 라이브러리를 사용하여 curry함수를 사용자가 직접 정의할 필요 없이, _.curry를 사용하여 함수를 인수로 전달하여 curriedSum을 정의하였습니다.

객체 데이터를 가져오는 커링

커링을 사용하지 않은 경우

const todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

const getTodosIdArr = todos => todos.map(todo => todo.id);
const getTodosContentArr = todos => todos.map(todo => todo.content);
const getTodosCompletedArr = todos => todos.map(todo => todo.completed);

console.log(getTodosIdArr(todos)); // [ 3, 2, 1 ]
console.log(getTodosContentArr(todos)); // [ 'HTML', 'CSS', 'Javascript' ]
console.log(getTodosCompletedArr(todos)); // [ false, true, false ]

일반적인 경우 코드를 작성하면 위와 같이 작성하게 됩니다. 여기에 커링을 적용하면 아래와 같이 코드를 작성할 수 있습니다.

커링 사용 후

const todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

const get = property => object => object[property];

const getId = get('id');
const getContent = get('content');
const getCompleted = get('completed');

const getTodosIdArr = todos => todos.map(getId);
const getTodosContentArr = todos => todos.map(getContent);
const getTodosCompletedArr = todos => todos.map(getCompleted);

console.log(getTodosIdArr(todos)); // [ 3, 2, 1 ]
console.log(getTodosContentArr(todos)); // [ 'HTML', 'CSS', 'Javascript' ]
console.log(getTodosCompletedArr(todos)); // [ false, true, false ]

커링을 사용하는 경우 인자의 순서는 중요한데, 앞에 존재하는 인자일 수록 변동가능성이 적고 뒤에 있는 인자일 수록 변동가능성이 높기 때문에 이 순서를 고려하여 코드를 설계하는 것이 중요하다고 합니다.

커링의 활용

커링 - ko.javascript.info 에는 커링의 이점을 이해하기 위해 하나의 사례를 가정하였습니다.

정보를 형식화하고 출력하는 로그함수가 있다고 가정하고, 실제 프로젝트에서 이러한 함수는 네트워크를 통해 로그를 보내는 것과 같은 많은 유용한 기능을 제공한다고 합니다.

function log(date, importance, message) {
  alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}

이곳에 lodash를 이용한 커링을 적용하여 아래와 같이 만들었습니다.

log = _.curry(log);


log(new Date(), "DEBUG", "some debug"); // log(a, b, c)
log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)

이 경우 둘다 정상적으로 작동이 됩니다.

이 이후 아래처럼 현재 시간으로 로그를 출력하는데 편리하도록 log함수를 작성해서 사용할 수 있습니다

// logNow는 log의 첫 번째 인수가 고정된 partial
const logNow = log(new Date());

logNow("INFO", "message"); // [HH:mm] INFO message

const debugNow = logNow("DEBUG");

debugNow("message"); // [HH:mm] DEBUG message

최종적으로 커링한 후에 잃은 것은 없으며, log는 아직 보통때처럼 호출할 수 있습니다. 더하여 partial함수를 쉽게 작성하여 고정 값들은 고정시켜 원하는 값만 도출되게하여 디버깅하는데 편리하게 해줍니다.

결론

아직 큰 프로젝트 단위의 코드를 작성해보지는 않았지만, 코드의 양이 많아지게되면 오류가 발생하였을 시 디버깅하는 과정이 매우 어려워 질 것입니다. 또한 여러 함수가 존재하거나, 추상화가 큰 함수가 존재하는 경우 안에서 여러 일을 할 가능성이 높기 때문에 함수 내부의 코드의 복잡도가 증가하여 이 함수가 무슨 함수인지 이해하는 시간이 오래걸릴 것입니다.

이러한 상황에서 함수형 프로그래밍과 커링 함수는 가독성을 높이고 함수의 작동방식을 명확하게 하여 유지보수를 하는데에 있어서 많은 도움을 줄수 있을 것입니다.

참고자료

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글