오늘은 함수와 함께 사용할 수 있는 커링이라는 고급기술을 알아볼려고합니다.
커링은 f(a, b, c)처럼 단일 호출로 처리하는 함수를 f(a)(b)(c)와 같이 각각의 인수가 호출 가능한 프로세스로 호출된 후 병합되도록 변환하는 것입니다.
커링은 함수를 호출하지 않습니다. 단지 변환할 뿐이죠.
다음과 같이 커링을 사용하여 각각의 인수를 넣어서 출력하는게 가능합니다.
// 커링 변환을 하는 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
lodash를 사용하면 좀 더 간단하게 커링을 구현할 수 있습니다. lodash는 자바스크립트 라이브러리로 객체나 리스트를 쉽게 튜닝할 수 있게 다양한 기능들을 제공합니다.
const _ = require('lodash');
const sum = (a, b) => a + b;
const curriedSum = _.curry(sum);
console.log(curriedSum(1)(2));
위에서 커링에 대한 기초적인 방식들을 알아보았지만 와닿지 않을 수도 있습니다.
정보를 형식화하고 출력하는 로그함수가 있다고 가정하고, 실제 프로젝트에서 이러한 함수는 네트워크를 통해 로그를 보내는 것과 같은 많은 유용한 기능을 제공한다고 합니다.
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함수를 분할하여 사용이 가능해지므로, message만, logLevel만 따로 따로 호출 하는 것도 가능해지는 겁니다.
// 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
기초에서는 간단하게 두 숫자를 받아서 합을 계산하는 커링기술을 알아봤습니다. 이번에는 함수식을 받고 인자의 길이를 체크하며 합을 진행하는 기능을 만들어 볼려고합니다.
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) { // (1)
return func.apply(this, args);
} else {
return function(...args2) { // (2)
return curried.apply(this, args.concat(args2));
}
}
};
}
function sum(a, b, c) {
return a + b + c;
}
let curriedSum = curry(sum);
alert( curriedSum(1, 2, 3) ); // 6, 보통때 처럼 단일 callable 형식으로 호출하기
alert( curriedSum(1)(2,3) ); // 6, 첫 번째 인수를 커링하기
alert( curriedSum(1)(2)(3) ); // 6, 모두 커링하기
input에 받는 문자열을 입력할때마다(oninput) 객체에 담을려고 합니다. 이때 입력할때마다 객체에 담기면 엄청 비효율적일 겁니다. 사용자가 문자를 다 입력하고 객체에 넘길 수 있도록 delay를 주려고 합니다.
let data = '';
let isExcute = null;
function curry() {
return function(event) {
return function() {
data = event.target.value;
console.log(data);
}
}
}
const myTimer = (trigger, ms) => {
if(isExcute) {
console.log("선실행 존재");
clearTimeout(trigger);
}
isExcute = setTimeout(trigger(event), ms);
}
//html
<input type="text" value="none" oninput = " myTimer(curry(), 3000)" />
선 실행이 존재하면 기존 trigger를 버리고, 새로운 trigger를 3초 대기상태로 만듭니다. 더이상의 입력이 없다면 data에 target.value를 저장하게 됩니다. 이처럼 setTimeout도 하나의 타이머를 정해두고 calculate, object setter, get api 등 여러가지 함수(여기서는 setter)를 재사용하게 가능합니다.
참고자료들___
(참고) 커링
(참고) 커링에 대해 알아보자