https://edykim.com/ko/post/writing-a-curling-currying-function-in-javascript/
https://medium.com/@kevincennis/currying-in-javascript-c66080543528
커링이라는 개념은 하스켈을 공부할 당시 알게 되었다.
clojure의 partial과 비슷한 개념이다. (동일한가?)
여튼 자바스크립트로 curry를 쓰고 싶은 욕구가 강했지만, underscore.js같은 라이브러리를 사용할 수 없는 제약이 있어,
다른 누군가가 어떻게 curry만을 구현했는지 확인하고 복붙을 하기로 하였다.
그 중에 위의 링크를 확인했고 하나하나 파고들어갔다. 아래 내용은 위 블로그를 읽고 나만의 부연설명을 추가한 것이다.
function curry(fn) { var arity = fn.length; // 함수의 필요 인자 // 매번 curry된 함수를 호출할 때마다 새로운 인자를 배열에 넣어 클로저 내에 저장한다. // 배열의 길이는 fn.length와 동일해야 한다. (실행될 때) // 혹여 인자의 수가 동일하지 않으면 새로운 함수로 반환한다. // 인자 목록을 가지는 클로저가 필요하다 (함수로 둘러쌓아야 한다 // 또 여기서 개별의 클로저가 생성되야 하니까 즉시 실행함수로 만든다.) // 전체인자(배열)과 fn.length를 확인 // 인자의 수가 부족하면 부분적으로 적용된 함수를 반환 // 인자의 수가 충족하면 fn에 모든 인자를 적용,호출하여 리턴 return (function resolver() { // 지금까지 받은 인자를 복사한다. // 이전 클로저가 오염되지 말게 var memory = Array.prototype.slice.call(arguments); // resolver는 익명함수를 반환한다. // resolver는 인자가 부족할 때 반환한다. // resolver가 새로 반환되는 이유는 클로저를 위한 것같다. // resolver는 바로 실행되고 익명함수를 하나 리턴한다. // resolver가 이전까지 모은 인자를 가지고 있다. (memory) // 이걸 변수에 담았다가 나중에 실행시킬 것이다. // 실행시키면 arguments에서 인자를 memory와 합체한다. // 그리고 원래 실행되어야할 함수(fn)이 필요로 하는 인자의 갯수와 비교 // 지금까지 모은 인자(local)과 arity의 길이가 맞다면 // 원래함수를 호출(fn), 그렇지 않으면 resolver를 다시 반환 (인자를 더 받는다) return function() { var local = memory.slice(); Array.prototype.push.apply(local, arguments); next = local.length >= arity ? fn : resolver; return next.apply(null, local); }; }()); }
한번 생성한 커리에 함수를 넣어보자. 지금 함수를 넣으면 인자 fn에 들어가는 것이다.
====
function volume(l, w, h) { return l * w * h; } var curried = curry(volume);
이제 아래 코드를 보면서 어떻게 되는지 보자.
function curry(fn) { var arity = fn.length; // 1. 숫자 3이 저장된다. return (function resolver() { var memory = Array.prototype.slice.call(arguments); // 2. resolver를 인자없이 실행하였다.(현재는 커리만 되는 상태) return function() { var local = memory.slice(); Array.prototype.push.apply(local, arguments); next = local.length >= arity ? fn : resolver; // 3. arity가 부족하므로 함수를 리턴. return next.apply(null, local); }; }()); }
함수를 리턴받아서 curried에 넣었다. 여기에 다른 인자들을 넣어보자.
일단 2를 넣을 것인데 l, w, h 총 3개가 필요한 함수에게는 부족한 인자 갯수이다.
var length = curried(2);
어떤 일이 일어나는지 아래를 보자.
function curry(fn) { var arity = fn.length; return (function resolver() { var memory = Array.prototype.slice.call(arguments); return function() { // 1. resolver로 반환된 익명함수가 실행됨. var local = memory.slice(); // 2. memory는 현재 0개의 인자를 가지고 있다. Array.prototype.push.apply(local, arguments); // 3.이번에 추가된 2가 local에 push된다. next = local.length >= arity ? fn : resolver; // 4.아직 인자가 1개이기 때문에 다시 resolver를 반환한다. return next.apply(null, local); // 4. 알다시피 이번에 던져지는 resolver는 또한 새로운 클로저와 함께하는 새로운 함수다. }; }()); }
한번 더
var lengthAndWidth = length( 3 );
function curry(fn) { var arity = fn.length; return (function resolver() { var memory = Array.prototype.slice.call(arguments); return function() { var local = memory.slice(); // 1. 새로운 클로저에 들어있는 memory는 [2] 이다. local에 복사한다. (이전 클로저에 해를 끼치지 않게) Array.prototype.push.apply(local, arguments); // 2. 새로운 인자 3을 추가한다. [2,3] next = local.length >= arity ? fn : resolver; // 3.아직 인자가 2개이기 때문에 다시 resolver를 반환한다. return next.apply(null, local); // 4. 알다시피 이번에 던져지는 resolver는 또한 새로운 클로저와 함께하는 새로운 함수다. }; }()); }
이제 마지막으로 하나의 인자만 더 넣으면 실행될 것이다.
console.log( lengthAndWidth( 4 ) ); // 24
왜 그렇게 되는지 아래를 보자.
function curry(fn) { var arity = fn.length; return (function resolver() { var memory = Array.prototype.slice.call(arguments); return function() { var local = memory.slice(); // 1. 새로운 클로저에 들어있는 memory는 [2,3] 이다. local에 복사한다. (이전 클로저에 해를 끼치지 않게) Array.prototype.push.apply(local, arguments); // 2. 새로운 인자 4을 추가한다. [2,3,4] next = local.length >= arity ? fn : resolver; // 3.아직 인자가 3개이기 때문에 fn을 넣는다. (fn은 지금까지 변경된 적도 사용된 적도 있다. 하지만 이날을 위해 기다렸다) return next.apply(null, local); // 4. 이번에는 함수를 던지지 않고 fn의 리턴값이 실행될 것이다. (하지만 fn이 리턴하는게 함수라면... 음...) }; }()); }
좋은 글 잘 보고 갑니다. ^^
답글삭제박영진