카테고리 없음

커링 함수

juundev 2024. 5. 23. 15:17

커링 함수에 대한 내용을 보다가 지연 실행이라는 개념에 대해 제대로 배웠습니다.

실제 동작하는 웹 앱 애플리케이션을 개발하다보면 지금 호출한 함수가 조건부로 실행되었으면 좋겠고, 재사용까지 할 수 있으면 좋겠다는 생각을 해봤다면 커링 함수를 쓰는 것이 좋을겁니다.

 

커링 함수란?

커링 함수(currying function)는 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 구조로 만든 함수입니다.

커링 함수를 실행하면 마지막 매개 변수를 받기 전까지 실행되지 않다가, 마지막 매개 변수를 입력하는 순간 메서드가 호출되고 종료된 후에는 가비지 컬렉터가 수거하여 메모리를 해제하는 원리입니다.

 

다음은 커링 함수의 기본적인 예시 코드입니다.

var currying = function(method) {
    return function(num1) {
        return function(num2) {
            return method(num1, num2);
        };
    };
};
// 받아야 하는 인자가 method, num1, num2이니까 
// 마지막 인수를 제외한 나머지 인수는 모두 전달해주어야 한다.
var getMax = currying(Math.max)(10); 
console.log(getMax(8)); // console.log: 10
console.log(getMax(25)); // console.log: 25

이처럼 커링 함수는 필요한 상황에 따라 직접 만들어 쓰기 용이합니다. 필요한 인자 개수만큼 함수를 만들어 계속 리턴해주다가 마지막에만 조합해서 리턴해주기 때문입니다.

 

하지만 이렇게 코드를 짜면 많은 인수를 받는 커링 함수는 꽤나 복잡할 것입니다.

인수를 6개 정도 받는 커링 함수만 생각해도 LOC가 15줄은 될 겁니다.

 

하지만 다행히도 ES6에서는 화살표 함수라는 개념을 도입해 한 줄로 써서 표기할 수 있습니다.

var currying = (method) => (a) => (b) => (c) => (d) => (e) => method(a, b, c, d, e);
var getMin = currying(Math.min);
console.log(getMin(1)(2)(3)(4)(5)); // console.log: 1

// 화살표 함수를 사용하면 위의 방법과 달리 
// 받아야 하는 인자를 최소 하나 이상 최대 받아야하는 인자 개수만큼
// 명시할 수 있습니다. 다음처럼 말이죠
var getMin2 = currying(Math.min)(1);
console.log(getMin(2)(3)(4)(5)); // console.log: 1

var getMin3 = currying(Math.min)(1)(2);
console.log(getMin(3)(4)(5)); // console.log: 1

이처럼 화살표 함수로 커링 함수를 구현하면 인자가 전달되는 과정을 이해하기 수월합니다.

화살표 순서에 따라 인자를 넘겨주고 마지막 인자가 전달되는 순간 method가 실행될 것이라는 흐름을 파악하기 쉽다는 말입니다.

 

이 커링 함수가 유용한 경우가 있는데, 앞서 언급했듯이 당장 필요한 정보만 받아서 전달하고 필요한 정보가 들어오면 전달하는 식으로 하면 함수 실행을 미루는게 되는데, 이를 함수형 프로그래밍에서 지연실행(Lazy Execution)이라고 칭합니다.

 

혹은 프로젝트 내에서 자주 쓰이는 함수의 매개변수가 항상 비슷하고 일부만 바뀌는 경우에도 적절한 함수가 될 것입니다.

제가 진행했던 퀴즈미(Quiz-me: 퀴즈를 만들고 친구들에게 공유하는 서비스)를 예시로 들어서 실제로 커링 함수가 어떻게 쓰이는지 확인해보겠습니다.

 

var getQuizzes = function(baseUrl) {
	return function(path) {
    	return function(uuid) {
        	return fetch(baseUrl + path + '/' + uuid);
    	};
    };
};

// ES6
var getQuizzes = (baseUrl) => (path) => (uuid) => fetch(baseUrl + path + '/' + uuid);

HTML5의 fetch 함수는 url을 받아 해당 url에 HTTP 요청을 합니다. 보통 REST API를 이용할 경우 baseUrl은 몇 개로 고정되지만 나머지 path(나머지 주소)나 id(쿼리 데이터) 값은 규모에 따라 매우 많을 수 있습니다.

이런 상황에서 서버에 정보를 요청할 필요가 있을 때마다 매번 baseUrl부터 전부 기입해주는 방법보다는 공통적인 요소를 먼저 기억시켜두고 특정한 값(id)만으로 서버 요청을 수행하는 함수가 있다면 개발 효율성이나 가독성, 유지보수 측면에서 더 좋을 것입니다.

 

다음은 위의 getQuizzes 커링 함수를 사용하는 예시 코드입니다.

* 예시 코드에서의 인자 값은 실제 사용되는 값이 아닌 예시 값입니다.

 

const quizUrl = 'http://quiz-me.com/';

// 요청 함수 준비
// baseUrl 입력
const getQuiz = getQuizzes(quizUrl);
// 퀴즈 데이터 조회 path 입력
const getQuizData = getQuiz('quizzes');
// 퀴즈 결과 조회path 입력
const getQuizResult = getQuiz('quizzes/result'); 

// 퀴즈 데이터 요청
// http://quiz-me.com/quizzes/D0s892819xsdc
const firstQuiz = getQuizData('D0s892819xsdc');
// http://quiz-me.com/quizzes/Sd019dasfksxd
const secondQuiz = getQuizData('Sd019dasfksxd');

// 퀴즈 결과 요청
// http://quiz-me.com/quizzes/result/D0s892819xsdc
const firstQuizResult = getQuizResult('D0s892819xsdc');
// http://quiz-me.com/quizzes/result/Sd019dasfksxd
const secondQuizResult = getQuizResult('Sd019dasfksxd');

이런 이유로 최근에 여러 프레임워크나 라이브러리 등에서 커링을 상당히 광범위하게 사용하고 있다고 합니다.

 

다음은 Flux 아키텍처의 구현체 중 하나인 Redux의 미들웨어를 예로 들어보겠습니다.

// Redux Middleware 'Logger'
const logger = (store) => (next) => (action) => {
	console.log('dispatching', action);
    console.log('next  state', store.getState());
    return next(action);
};

// Redux Middleware 'thunk'
const thunk = (store) => (next) => (action) => {
	return typeof action === 'function'
    	? action(dispatch, store.getState)
        : next(action);
};

위의 두 미들웨어는 공통적으로 store, next, action 순서로 인자를 받습니다. 

이 중 store는 프로젝트 내에서 한 번만 생성되고 그 이후로는 바뀌지 않는 값이고 Dispatch의 의미를 가지는 next도 역시 마찬가지지만, action의 경우 상황에 따라 매번 달라집니다. 그러니까 store와 next 값이 결정되면 Redux 내부에서 logger 또는 thunk에 store, next를 미리 넘겨서 반환된 함수를 저장시키고, 이후에는 action만 받아서 처리할 수 있게끔 구현한 것입니다.

 

코어 자바스크립트: 핵심 개념과 동작 원리로 이해하는 자바스크립트 프로그래밍을

공부하며 이해한 내용을 기반으로 정리한 게시글입니다.