쉽고 빠르게 커링(Currying) 기법 이해하기(feat.자바스크립트)

커링(Currying) 기법의 이해와 사용, 그리고 장단점

커링(Currying)은 카레와 같은 스펠링을 갖고 있지만 그 유명한 하스켈(Haskell)이라는 이름의 출처인 수학&논리학자 하스켈 브룩스 커리(Haskell Brooks Curry)의 성(Family Name)입니다.

커링은 하나 이상의 매개변수(Parameter)를 갖는 함수를 부분적으로 나누어 각각 단일 매개변수를 갖는 함수로 설정하는 기법입니다.

식으로 설명하면 다음과 같습니다.

func(a, b, c) -> f(a)(b)(c)

위 식을 코드로 변경하면 함수를 리턴하면서 다음 매개변수를 받아 또 함수를 리턴합니다.

//non-curried
function plusFunc(a, b, c){
  console.log(a + b + c);
}

plusFunc(1, 2, 3);   // 6

//curried
function plusFunc(a){
    return function(b){
       return function(c){
          console.log(a + b + c);
       }
    }
}

plusFunc(1)(2)(4);  // 7

논커리와 커리의 차이를 확인해 보겠습니다.

non-curried : 함수 실행 시 파라미터가 모자라도 문제 없이 실행이 가능함
? 함수 정의 : func(a, b, c)
? 함수 실행 : func(a)
? 실행 결과 : func(a, undefined, undefined)

curried : 함수가 인수를 전부 받을 때까지 실행을 보류함.
? 함수 정의 : func(a, b, c)
? 함수 실행 : func(a)
? 실행 결과 : func(a)상태에서 b 함수 입력 대기

위 특징에 따라 다음과 같이 사용할 수 있습니다.

function setAppell(appell){
    return function(name) {
       console.log(appell + name);   
    }
}

const setName = setAppell('Mr.');
setName('Right');  // Mr.Right
setAppell('Mr.')('Baker'); //Mr.Baker

//ES6 화살표 함수
const setAppell = appell => name => console.log(appell + name);

const setName = setAppell('Miss.');
setName('Dior');  // Miss.Dior

또한 다음과 같이 이벤트와 파라미터를 동시에 전달할 때도 유용합니다.

const setNumber = (num) => (event) => {
     console.log(event.target.id+':'+num);
}

<div onClick="setNumber(2)(event)" id='myNum'>
  click
</div>

// myNum:2

그럼 커링의 장점을 요약하면 무엇이 있을까요?

장점?
재사용 가능
– 생산성 향상(코드 양 감소)
– 가독성 향상

그렇다면 커링의 단점은 무엇일까요?

단점?
– 함수가 깊이 깊이 중첩되면 메모리를 과다하게 점유할 가능성이 있는 것과 같은 맥락에서 커링을 과용하면
메모리와 속도에 문제점 발생 가능성이 있음

커링의 수학적인 설명이 필요하신 분을 위한 링크



커링을 사용하지 않아도 원하는 기능을 대부분 구현할 수 있지만 재사용성이나 가독성에 따른 생산성 향상을 위해 커링을 사용하면 좋은 효과를 기대할 수 있습니다. 커링을 직접 구현하지 않더라도 커링의 개념과 사용법에 대해 알아두면 다른 사람의 코드를 읽거나 로직을 이해하는데 큰 도움이 될 것이라 생각합니다.

자바스크립트 클로저 파헤치기(What is Javascript Closure)

Venezia

자바스크립트에서 변수는 전역(global)변수와 지역(local)변수가 있어요.

전역변수는 위치에 상관없이 모든 함수가 접근 가능하지만 지역변수는 해당 스코프 내부에서만 접근이 가능합니다.

하지만 클로저를 사용하면 전역변수처럼 지역변수를 사용할 수 있어요. 안전성을 보장하면서요.

전역변수

함수는 함수 내 선언된 모든 변수에 접근할 수 있어요.

샘플1


function testFunction( ) {

  let abc = 10;

   return abc * 2;

}


또한 함수 외부의 변수에도 접근할 수 있어요.

샘플2


let abc = 10;

function testFunction( ) {

   return abc * 2;


샘플2에서 abc는 전역변수입니다. 웹 페이지에서 전역 변수는 window 객체에 속합니다. 

전역변수는 페이지 내 모든 스크립트가 접근하여 사용하거나 변경할 수 있어요.

샘플1에서 abc는 지역변수입니다. 지역변수는 변수를 선언한 함수 내에서만 사용이 가능해요. 

다른 함수나 스크립트 코드에게는 감춰져 있는 것이죠.

전역변수와 지역변수는 이름이 같아도 다른 변수입니다. 하나를 수정해도 다른 변수는 영향을 받지 않죠.

선언자(var, let, const) 없이 생성된 변수는 모두 전역변수입니다. 특정 함수 안에서 생성 되었다고 해도 말이죠.

function testFunction() {

   abc = 10;

}

변수의 생명 주기(lifetime)

전역변수는 페이지가 종료될 때(다른 페이지로 넘어가거나 윈도우 창을 종료)까지 유효합니다.

그리고 지역변수는 함수가 호출될 때 생성되어 함수가 종료될 때까지 유효합니다.

숫자 카운팅의 문제

모든 함수에서 접근이 가능한 변수를 만들어 숫자 카운팅에 사용해보도록 하겠습니다.

전역 변수와 함수를 사용해 숫자를 카운팅합니다.

샘플3


let cnt = 0;

function addNum(){

   cnt += 1;

}

addNum();

addNum();      

// 결과:2


하지만 위의 코드는 addNum() 함수가 아닌 다른 코드로도 cnt 변수에 접근하여 수정이 가능합니다. 

따라서 cnt 변수는 addNum() 함수 내부에 존재해야 다른 코드에서 접근할 수 없습니다.

샘플4


let cnt = 0;

function addNum() {

   let cnt = 0;

  cnt += 1;

}

addNum();

addNum();

// 결과는 2일 것 같지만 0이 반환


같은 이름의 전역변수를 선언하였기 때문에 결과는 2가 아닌 0이 됩니다. 

그럼 함수의 반환값을 사용해 전역변수를 사용하지 않는 접근 방법을 확인해보겠습니다.

샘플5


function addNum() {

   let cnt = 0;

   cnt += 1;

   return cnt;

}

addNum();

addNum();

// cnt는 2가 되어야 하지만 결과는 1이 반환


함수 내부에 선언한 지역변수는 함수 호출 시마다 리셋되므로 의도한대로 작동하지 않습니다.

자바스크립트는 이 문제를 내부 함수(inner function)를 사용해 해결합니다.

자바스크립트의 중첩 함수(Nested function)

모든 함수는 전역 스코프에 접근할 수 있습니다. 

그리고 자바스크립트의 모든 함수는 자신을 감싸고 있는 부모 스코프에 접근이 가능합니다. 

자바스크립트는 중첩 함수를 지원하며, 중첩 함수 역시 자신을 감싸고 있는 부모 스코프에 접근이 가능합니다.

아래 샘플을 확인해보면 내부함수 plus()는 부모 함수에 있는 cnt 변수에 접근이 가능합니다.

샘플6


function addNum() {

   let cnt = 0;

   function plus() {

      cnt += 1;

   }

   plus();  

   return cnt;

}


cnt = 0을 한번만 선언할 방법과 plus() 함수에 접근할 수 있는 방법을 찾으면 위에서 확인한 숫자 카운팅의 딜레마 문제를 해결할 수 있습니다.

여기서 우리는 클로저를 사용할 수 있습니다.

클로저를 확인하기 전에 먼저 즉시실행함수(IIFE, Immediately Invoked Function Expressions)에 대해 간략히 알아보겠습니다.

즉시실행함수는 호출없이 자동으로 실행되는 함수로, 함수를 괄호로 묶고 반드시 끝에 ()를 붙여야 합니다.

아래는 즉시실행함수의 표현식 샘플입니다.

샘플7


(function() {

  let abc = “self-invoking”;

})();


그럼 이제 클로저를 확인해보겠습니다.

Austria(2013)

자바스크립트 클로저(Closure)

클로저는 즉시실행함수를 이용합니다.

샘플8


const addNum = (function() {

   let cnt = 0;

   return function() {

   cnt += 1

   return cnt;

   })();

addNum();

addNum();

// 결과:2


addNum 변수에는 즉시실행함수의 반환값이 할당됩니다. 

즉시실행함수는 한번만 실행되며, cnt를 0으로 선언한 후 함수 표현식을 반환합니다.

이를 통해 addNum은 함수가 되며, 부모 스코프가 종료되어도 부모 스코프에 존재하는 cnt 변수에 접근이 가능하게 됩니다.

이것이 바로 자바스크립트의 클로저입니다. 클로저는 이와 같이 함수에서 상태 유지가 가능한 개별적인 변수들을 갖도록 합니다. 

cnt는 익명함수의 스코프에 의해 보호받으며, 오직 addNum 함수만을 사용해 접근이 가능합니다.


클로저는 부모 함수가 종료된 후에도 부모 스코프의 변수에 접근이 가능한 함수로, 특정 함수만 변수에 접근할 수 있도록 하기 위한 안전 장치라고 보면 됩니다. 
참고로 클로저로 접근하는 변수와 부모 스코프에 존재했던 변수는 완전히 같은 공간을 참조하는 것은 아니고 각각 독립적인 객체입니다.


참고자료 https://www.w3schools.com/js/js_function_closures.asp