Contents

커링의 개념과 이해

https://towardsdatascience.com/what-is-currying-in-programming-56fd57103431

이름의 유래

당연하게도 카레나 농구 선수로부터 이름을 딴 것은 아니고,

전설적인 수학자 Haskell Curry의 이름을 따서 지어졌다. Haskell? 했다면 맞다.. 프로그래밍 언어 Haskell도 이 분의 이름을 따서 지어졌다

그래서 currying이 뭔데

currying은 다수의 인자를 가진 함수를 단일 인자를 가지는 함수의 시퀀스로 변환하는 것이다. 즉 f(a, b, c, ...)와 같은 형태의 함수를 f(a)(b)(c)...와 같이 바꾼다.

파이썬의 functools.partial 과도 비슷하다.

자바에도 currying이 존재한다. (난 이게 ‘진짜’ 커링이라고는 생각하진 않는다)

https://github.com/iluwatar/java-design-patterns/tree/master/currying

(위 자바 예시에서는 빌더 패턴을 예시로 들었지만, 실제로 대부분의 빌더 패턴은 빌더 클래스를 통해서 객체를 생성하는 경우가 많았던 것 같다. )

함수형 프로그래밍의 향기가 나는 프로그래밍 언어라면 currying의 개념이 널리 퍼져서 사용되고 있는 것 같다.

어떻게 쓰나

두 수 a, b의 대소를 비교하는 다음과 같은 함수가 있다고 가정해보자.

function isGreaterThan(a, b) {
	return b > a;
}
  • 위 함수를 사용하려면 아마도 isGreaterThan(2, 5)와 같이 사용할 것이다.
function isGreaterThan(a, b) {
	return function(b) {
		return b > a;
	}
}
  • 위 함수의 currying 버전 함수는 isGreaterThan(2)(5)와 같이 사용할 수 있다.

커링의 멋진 점은 가장 함수형 프로그래밍 다운 문법을 사용할 수 있다는 것이다. 실제로 위 커링 함수 isGreaterThan은 다음과 같이 표현할 수도 있다.

const isGreaterThan = a => b => b > a;

왜 쓰나

커링은 범용적인, 복잡한 인자를 받는 함수를 작성해야 하는 많은 경우에 편리한 기능을 제공한다. 코드를 더 가독성 좋고 자세하게 만들어주기도 한다.

1. 빈번한 함수 호출이 있을 때

function log(type, msg) {
	if (type == "error") {
		console.error(msg);
	}
	if (type == "warn") {
		console.warn(msg);
	}
	if (type == "info") {
		console.info(msg);
	}
}

위와 같은 함수가 있을 때, 커링을 적용하여 다음과 같이 사용할 수 있다.

function curry(func) {
	return function(a) {
		return function(b) {
			return func(a, b);
		}
	}
}

logger = curry(log);
const error = logger("error");
const warn = logger("warn");
const info = logger("info");

curry의 첫 번째 인자로 타입이 들어왔으므로, error(msg) 와 같이 호출하기만 하면 함수의 재사용성은 유지하면서, 편리하고 간편하게 함수를 호출할 수 있게 된다.

2. 배열의 누적합

다음과 같은 배열 2개가 있다고 가정해보자.

input: [5, 23, 6, 7, 34] output: [5, 28, 34, 42, 76]

뭐.. 배열이기 때문에 reduce를 사용할 수도 있겠지만, 커링 함수를 사용해보자면

let cumulativeSum = sum => value => sum += value;
let sumArr = array.map(cumulativeSum(0));
  1. 아주 아름답게 한 줄로
  2. 배열의 누적합을 구하는
  3. 재사용성이 있는 cumulativeSum 함수를 만들 수 있었다.

커링 래퍼 만들기

function curryWrapper(func) {
	return function curried(...args) {
		if (args.length >= func.length) {
			return func.apply(this, args);
		}
		return (...args_) => curried.apply(this, args.concat(args_));
	}
}
  • 입력 받은 인자가 함수의 인자보다 더 많거나 같은 경우 그냥 함수를 입력 받은 인자로 호출한다.
  • 입력 받은 인자 개수가 함수의 인자보다 적은 경우 해당 인자를 사용하여 내부 함수를 재귀적으로 호출, 커링 함수로 만든다.

어떻게 동작하나

커링은 클로저와 lexical environment의 개념을 적극적으로 활용한 예시에 가깝다.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

이쯤에서 다시 리마인드하자면…

  • lexical environment는 함수가 생성되었을 때의 스코프 내의 로컬 변수로 구성되어 있다.
  • 클로져를 사용하면 발견된 상태의 함수의 모든 로컬 변수를 참조할 수 있다.
  • 함수 내부에 함수를 선언하는 행위는 근본적으로는 클로져에 해당한다.

이 로컬 변수들은 전역적으로 선언된 변수에 연결하거나, 상위 함수에서 클로져를 반환하여 전역 범위에서 참조할 수 있다.

https://www.digitalocean.com/community/tutorials/an-introduction-to-closures-and-currying-in-javascript

왜 클로져를 사용할까

클로저를 사용하는 행위 자체에는 장단점이 모두 존재한다.

ES6 이전 문법에서는 클로저는 OOP에서 제공해주었던 전통적인 개념의 클래스를 사용할 수 있는 것처럼 하게 해주었다. (내부 함수 변수들은 클로져 내부에서만 참조 가능하므로, private처럼 사용 가능했다)

  • 이를 모듈 패턴이라고 부르기도 한다.
  • 별도의 namespace 오염 없이 재사용 가능하고 유지보수가 용이한 코드를 작성하는데 도움이 되었다.
var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
};

var counter1 = makeCounter();
var counter2 = makeCounter();

counter1.value(); // returns 0
counter1.increment(); // adds 1
counter1.increment(); // adds 1
counter1.value(); // returns 2
counter1.decrement(); //subtracts 1
counter1.value(); // returns 1
counter2.value(); // returns 0

또한 클로져를 사용함으로써 함수를 사용하여 인자에 특별한 값을 넣은 채로 다른 함수를 생성할 수도 있다.

  • 이 경우에 부모 함수는 function factory라고도 불린다. (다른 함수를 생성하기 떄문)
  • 커링은 근본적으로는 클로져를 사용한 function factory를 사용하여 얻을 수 있는 행위이다.

ES6 부터는 클래스 문법이 생겼기 때문에 클로저가 이전처럼 OOP 처럼 js를 사용하는데 쓰이지는 않게 되었지만, 클린하고 재사용가능한 코드를 작성하기 위해서는 여전히 사용된다는 것이 커링의 존재로써 증명된 셈이다.

클로저와 커링은 OOP의 private 메서드와 본질적으로 유사한 개념을 수행하는 함수형 프로그래밍과 관련하여 이해해야 하는 중요한 개념이다.