Curry & Partial Application

DatQueue·2022년 9월 6일
0
post-thumbnail

시작하기에 앞서

우연히 타입스크립트의 “튜플”에 관해 다루던 중 “Partial Appliacation”이란 개념이 등장하였고 모르고 넘어가면 섭섭하면서 동시에 찝찝하므로 다뤄보고자 한다.

해당 내용을 다루게 된 동기가 이상하긴 하지만 섭섭지 않게 배워보고자 한다.

여태껏 객체지향적인 프로그래밍과 사고속에서 코딩을 공부해와서 해당 “함수형 프로그래밍”이 굉장히 어색하게 느껴질테지만 천천히 알아보고자 한다.


함수형 프로그래밍 — Curry(커링)

커링(curry)은 프로그래밍 기법의 이름이다.

커링의 원리는 수학의 방정식에서 유래되었다.

자바스크립트로 만든 아래의 add함수를 보자.

const add = (x,y) => x + y;

addxy를 받아서 두 변수의 합을 반환한다. 이를 수학적으로 표기하면 다음과 같다.

f(x,y) = x + y      // 흔히 우리가 중.고등 시절에 본 함수 식이다. 

그리고 f(x,y)역시 아래와 같이 표기할 수도 있다.

f(x,y) = h(x)(y) = x + y

여기서 hf를 다시 변환한 형태인데, 값을 하나 받아서 다른 값을 받는 함수를 반환하는 형태의 함수이다. 만약, x = 2를 만족한다면 식은 아래와 같을 것이다.

f(2,y) = h(2)(y) = 2 + y

f(2,y)는 물론 h(2)(y)또한 우항 2 + y와 동치이다. y라는 변수를 받아 값을 반환하는 함수인 것이다.

이런 아주 간단한, 학창시절 수학시간에 배운 개념으로부터 “커링”의 이해를 시작할 수 있다.

다시 , 자바스크립트로 작성한 add함수를 살펴보자.

const add = (x,y) => x + y;

만일 addf(x,y)형태로 표현할 수 있다면, addh(x)(y)의 형태로도 표현할 수 있어야 한다.

// Arrow function
const add = x => y => x + y;

function add(x) {
  return function(y) {
    return x + y;
  }
}

자바스크립트는 함수가 일급 객체로 취급되기 때문에 함수가 함수를 리턴할 수 있다.

해당 내용은 고차함수라고도 부르는 HOF(Higher-Order-Function)라는 개념인데, 지금 해당 내용에 관해서 다루긴 방대하므로 따로 찾아보길 바란다. HOF 또한 함수형 프로그래밍에 쓰이는 개념이므로 알아둘 필요가 있다.

일단 위와 같이 표현된 add함수를 보면 아래와 같이 사용될 수 있다.

function add(x) {
  return function (y) {
    return x + y;
  };
}

const addFive = add(5);
addFive(7); //12

vsCode의 기능을 통해 정의를 살펴보면

add함수를 호출한 변수 addFive함수 또한 y를 매개변수로 가지는 함수인 것을 바로 알 수 있다.

즉, 우리는 addFive(7)을 통해 5 + 7을 계산할 수 있는 것이다.

이 때, 꼭 알아야 할 특징은 함수를 호출하는 과정에 있어서 (물론 선언할 때도) 함수의 인자는 각 함수마다 오직 “하나”만 받을 수 있다.

결국, 커리라는 것은 하나의 인자를 받는 함수를 리턴하는 함수를 반환하는 함수를 반환하는 … (반복 가능) 함수이다. 매 턴 마다 받을 수 있는 인자는 오직 개 뿐이다. 그리고 개의 인자를 받는 해당 함수가 반환하는 함수 역시나 개의 인자를 받는다.


함수형 프로그래밍 — Partial Appliacation

앞서 보았던 add함수를 다시 살펴보자.

const add = (x,y) => x + y;

현재 add함수는 xy 이렇게 두 개의 인자를 받는다. 만약 개를 받는다면 어떨까?

const add = (x,y,z) => x + y + z

위의 구문을 “커리”로 표현한다면 어떨까?

//arrow
const add = (x) => (y) => (z) => x + y + z;

//normal
function add(x) {
  return function (y) {
    return function (z) {
      return x + y + z;
    };
  };
}

const addFive = add(5);
const addSeven = addFive(7);  //12
const addEight = addSeven(8); //20
console.log(addEight);  //20

위와 같이 “커리” 구문으로 나타낼 수 있는데, 여기서 조금 더 단계를 세분화해 표현해보자.

const add = x => (y,z) => x + y + z

먼저 인자 하나를 받아야 하니까 x를 받도록 하고, 나머지(여기선 y, z)는 리턴되는 함수가 받도록 처리한다. 그리고 해당 함수를 다시 풀어헤치는 것이다.

자, 방금 “Partial Application”을 설명했다. 위의 구문이 바로 파셜 애플리케이션 함수이다. 커리가 아니다.

갑자기 커리를 설명하다가 뜬금없이 이것이 “Partial Application”이라고 해서 굉장히 당황스러울 것이다. 하지만 분명히 저것은 커리가 아닌 “Partial Application”이다.

도대체 어떤 차이가 있는 것일까?


Curry vs Partial Application — 공통점과 차이점

  • Currying : 하나의 인자를 받는 함수로 표현하며 그 함수는 다른 함수를 리턴하고, 리턴된 함수 역시 하나이 인자를 받는 규칙을 따른다.

  • Partial Application : 함수를 리턴하는 함수이지만 함수가 받는 인자는 꼭 하나일 필요는 없다. 여러 개를 받는 것이 가능하다.

// Currying function
const add = x => y => z => x + y + z;

// Partial Application function
const add = x => (y,z) => x + y + z;
  • 공통점 : 커링과 파셔 애플리케이션 모두 원래 함수가 아닌 새로운 함수로 재탄생 되는 점에서 같다고 볼 수 있다. 그리고 새롭게 리턴되는 함수들이 받는 인자는 기존 함수가 받는 인자보다 최소한 1개 이상 받는 점에서 같다. 해당 문장을 조금 더 쉽게 말하자면 기존 함수는 아래의 꼴이고
    const add = (x,y) => x + y;
    새롭게 리턴되는 함수들은 아래의 꼴이다.
    const add = x => y => x + y; 
    (x,y) 두 인자를 받는 기존의 함수에 비해 커링과 파셜 애플리케이션은 x, y 하나의 인자를 받는다. 즉, 적어도 1개 이상 인자를 받는다고 할 수 있다.
  • 차이점 : 커링은 모든 함수가 하나의 인자만을 받는 대신에, 파셜 애플리케이션은 여러 개를 받을 수도 있다.

Curry & Partial Application — 왜 필요한가?

커링과 파셜 애플리케이션은 모두 게으른 평가 를 가능케 한다.

잠깐 “게으른 평가(Lazy Evaluation)”에 대해서 알아보자. 대부분의 언어에서 expression을 만나면

“즉시 평가”하지만, 항상 효과적이진 않다. 예를 들어, 함수가 전달된 파라미터를 사용하지 않는 경우, 컴파일러가 이를 평가할 필요는 없다.

간단한 아래 코드를 보자.

function add(x,y) {
  return x + 1;
}

add(2,3)

위 함수 add는 파라미터로 x, y 두 개를 가지지만 expression에선 y는 필요로 하지 않는다. 즉, 함수를 호출하는 구문 add(2,3)에서 3은 필요가 없다.

이때, 즉시 평가하지 않고, 필요한 것만 평가하는 방법을 “게으른 평가, 혹은 지연 평가(lazy evaluation)”라 하고, 즉시 평가하는 방법을 “strict evaluation”이라 한다.

게으른 평가(지연 평가)는 계산의 결과 값이 필요할 때 까지 계산을 늦추는 기법이다. 필요할 때 까지 계산을 늦추어 불필요한 계산을 줄임과 동시에 성능적인 면에서 효율성을 획득할 수 있다.

해당 “Lazy Evaluation”에 관한 자세한 내용은 따로 찾아보길 바란다.

그럼 아래 코드를 통해 실용적 사용 이유를 알아보자.

const getRGB = r => g => b => `rgb(${r},${g},${b})`;  // use Curring

//`rgb(0,0,${b})`
const getBlue = getRGB(0)(0); // do not get red & green
const div = getElementById("myDiv");

div.addEventListener('click',()=>{
  const randomColor = Math.floor(Math.random() * 255) + 0;
  div.style.background = getBlue(randomColor);
});

html에서 셀렉터로 지정한 #mydiv를 클릭할 때 마다 파랑 계열의 색으로 배경 색이 바뀌는 기능을 수행하는 코드이다. 먼저 커리구문으로 getRGB 를 설정해놓고 원하는 색깔만 받기 위해 getBlue, getRed, getGreen등을 설정할 수 있다. 커링으로 만들게 되면서 우린 DOM 엘리먼트가 클릭될 때 마다 모든 값(red , green , blue)의 평가를 할 필요가 없기 때문에 성능적인 측면에서 훨씬 효율적이다고 할 수 있다.


생각 정리

이처럼 우리는 커링파셜 애플리케이션을 통해 게으른 평가(혹은 지연 평가)에 대해 알아보았다. 물론 게으른 평가에 관해 깊게 알아보지는 않았지만 이 글을 통해 충분히 어떠한 원리인지는 이해가 갔을거라 생각한다. 커링과 파셜 애플리케이션의 핵심은 게으른 평가를 가능케 하고, 두 함수는 중첩되있는 함수(리턴되는 여러 개의 함수들)의 인자 갯수에 따라 구분을 할 수 있다는 점이지 않을까 싶다.

profile
You better cool it off before you burn it out / 티스토리(Kotlin, Android): https://nemoo-dev.tistory.com

0개의 댓글