Currying

raccoonback·2020년 7월 1일
1

javascript

목록 보기
11/11
post-thumbnail

Currying 란?

Currying다중 인수를 갖는 함수를 단일 인수를 갖는 함수들의 함수열로 바꾸는 것을 말한다.
예를 들어Curryingfoo(a, b, c)와 같은 단일 호출로 처리하는 함수foo(a)(b)(c)처럼 사용하는 방식이다.
즉, 중첩된 Currying 함수로 각각의 인수를 하나씩 전달받고 필요한 인자를 모두 얻으면 원래의 함수(foo)를 실행하는 것이다.

아래 예제를 보자.

const curry = function (fn) {
    return function (message) {
        return function (name) {
            return fn(message, name);
        }
    }
};


function greet(message, name) {
    return `${name} 님, ${message}`;
}

// 일반 함수 호출
let result = greet('안녕하세요.', '철수');
console.log(result);    // 철수 님, 안녕하세요.

// currying
const curryGreet = curry(greet);
result = curryGreet('누구세요.')('미애');
console.log(result);    // 미애 님, 누구세요.

currying은 원래의 함수(fn)를 전달받고 나서, 내부적으로 중첩된 두 개의 함수를 선언하였다.
중첩된 함수 선언을 통해서 필요한 인자인 fn, message, name을 모두 전달받는데, 각각의 인자는 렉시컬 스코프를 가지기 때문에 클로저 함수에서 모두 참조가 가능하다.
결과적으로 원래의 함수인fn는 전달받은 각각의 인자인 message, name로 실행되는데, 함수는 호출할 때마다 필요한 인자를 넘겨준다.

그럼, 함수 인자를 함수 호출로 나눠서 전달함으로써 얻게 되는 이점은 무엇일까?

왜 필요한가?

Currying의 가장 큰 장점은 재사용성이다.

뿐만 아니라, Currying은 함수의 인자를 미리 설정해 다른 역할을 하는 새로운 함수를 만든다.
즉, Currying을 통해 생성된 새로운 함수가 기존보다 한정된 기능만 수행하도록 행동을 구체화한다.

위 예제를 다시 살펴보자.

// 일반 호출
let foo = greet('안녕하세요.', '철수');
let bar = greet('안녕하세요.', '미애');
let baz = greet('안녕하세요.', '짱구');

console.log(foo, bar, baz);
// 철수 님, 안녕하세요. 미애 님, 안녕하세요. 짱구 님, 안녕하세요.

// currying
const curryGreet = curry(greet);
const greets = curryGreet('안녕하세요.');

foo = greets('철수');
bar = greets('미애');
baz = greets('짱구');

console.log(foo, bar, baz); 
// 철수 님, 안녕하세요. 미애 님, 안녕하세요. 짱구 님, 안녕하세요.

'안녕하세요.' 문구는 렉시컬 스코프를 가지는 curryGreet() 함수의 message에 미리 할당하기 때문에 불필요한 중복을 제거할 수 있다.
따라서,'안녕하세요.' 문구를 완성해주는 greets() 함수의 재사용성이 높아지고, 코드가 보다 간결해지는 것을 알 수 있다.

또한 curryGreet('안녕하세요.') 함수 호출을 통해, message 인자를 '안녕하세요.'로 한정한 새로운 함수인 greets()를 반환한다.
결과적으로, 기존의 greet(message, name) 함수보다 구체적인 행동을 수행하는 greets(name) 함수를 만들 수 있는 것이다.

bind() 함수

명시적인 This Binding을 위해 사용하는 Javascript 내장 함수인 bind()도 실은 currying 작업을 하는 녀석 중 하나이다.

const greets = greet.bind(Object.create(null), '안녕하세요.');

foo = greets('철수');
bar = greets('미애');
baz = greets('짱구');

console.log(foo, bar, baz);
// 철수 님, 안녕하세요. 미애 님, 안녕하세요. 짱구 님, 안녕하세요.

범용 Curry 함수

그럼 이제 앞선 예제를 일반화하여 범용적으로 사용 가능하도록 curry 함수를 변형해보자.

const curry = (fn) => {
    return function curryFn(...args) {
        if (args.length >= fn.length) {
            return fn(...args);
        } else {
            return (...factors) => curryFn(...args, ...factors);
        }
    }
};

function greet(message, name) {
    return `${name} 님, ${message}`;
}

// 일반 함수 호출
let result = greet('안녕하세요.', '철수');
console.log(result);    // 철수 님, 안녕하세요.

// currying
const curryGreet = curry(greet);
result = curryGreet('누구세요.')('미애');
console.log(result);    // 미애 님, 누구세요.

원래의 함수가 요구하는 인자의 수(Function.length)가 모두 전달될 때까지 재귀적으로 curry 함수를 생성한다. 그리고 함수 호출에 필요한 인자가 모두 전달되면 curry 함수를 만들지 않고 원래의 함수를 호출한다.

curry 함수의 동작 방식을 살펴보면 다음과 같다.
누적된 인자 수(args.length)가 fn 함수의 인자 수(fn.length)보다 같거나 많은 때까지, 전달받은 인자(factors)를 args 배열에 하나씩 추가해 curryFn 함수를 호출한다. 즉, 누적된 인자 수(args.length)가 함수 인자의 수(fn.length)보다 같거나 많을 때까지, curryFn 함수는 매번 인자를 하나씩 전달받아서 호출된다.
마침내 인자 수가 같아진다면, 누적된 인자(args)를 이용해 원래의 fn 함수를 호출한다.

참고 자료

profile
한번도 실수하지 않은 사람은, 한번도 새로운 것을 시도하지 않은 사람이다.

0개의 댓글