Currying

클로저를 공부하는데 커링이라는 개념이 나왔다.😱 커링이 도대체 뭐야…

여태 공부하면서 들어본 적 없는데 개념이라 정리를 해놔야겠다라는 생각이 들었다!

🙋🏻‍♂️ 커링(Currying) 함수란?

간단하게 말하자면, 여러 개의 인자를 가지는 함수를 하나의 인자를 받는 함수로 분해하는 것.

함수의 인수를 부분적으로 적용할 수 있는 함수를 구성하는 방법이다. 즉, 함수가 예상하는 모든 인수를 전달하고 결과를 얻거나, 인수의 하위 집합을 전달하고 나머지 인수를 기다리는 함수를 반환할 수 있다.

Haskell and Scala과 같은 함수형 개념을 중심으로 구축된 언어에서는 기본적으로 내장되어 있지만, 자바스크립트는 내장되어있지 않다. 하지만 자바스크립트도 커링을 구현할 수 있다.

🙋🏻 커링이 무엇인지 이해해보자.

어떻게 작동하는지 이해하기 위해 누군가에게 인사하는 함수를 만들어보자.

1
2
3
4
5
var greet = function (greeting, name) {
console.log(greeting + ',' + name);
};

greet('Hello', 'Jaerin'); // Hello, Jaerin

👉🏻 이 함수가 제대로 동작하려면 이름과 인사말을 모두 인수로 전달해야한다.

간단한 중첩 커링을 사용하여 함수를 재작성하면 기본 함수에는 인사말만 필요하고 인사하려는 사람의 이름을 받는 다른 함수를 반환하도록 할 수 있다.

First Curry

1
2
3
4
5
var grretCurried = function (greeting) {
return function (name) {
console.log(greeting + ',' + name);
};
};

함수를 작성하는 방식을 조금 조정하면 인사말 유형의 함수를 하나 만들고, 새 함수에 인사말을 건네고자 하는 이름을 전달할 수 있다.

1
2
3
var greetHello = greetCurried('Hello');
greetHello('Heidi'); //"Hello, Heidi"
greetHello('Eddie'); //"Hello, Eddie"

또한 각 매개변수를 괄호 안에 하나씩 전달하여 원래 greetCurried 함수를 호출할 수도 있다.

1
greetCurried('Hi there')('Howard'); //"Hi there, Howard"

Curry All the Things!

멋진 점은 이제 인수를 처리하는 데 이 접근 방식을 사용하도록 기존 함수를 수정하는 방법을 배웠으므로 원하는 만큼의 인수를 사용하여 이 작업을 수행할 수 있다.

1
2
3
4
5
6
7
8
9
var greetDeeplyCurried = function (greeting) {
return function (separator) {
return function (emphasis) {
return function (name) {
console.log(greeting + separator + name + emphasis);
};
};
};
};

위 예제에서 네 개의 인자를 사용했다. 중첩된 횟수와 상관없이 name 파라미터를 바꿔 새 함수를 만들어 보자.

1
2
3
var greetAwkwardly = greetDeeplyCurried('Hello')('...')('?');
greetAwkwardly('Heidi'); //"Hello...Heidi?"
greetAwkwardly('Eddie'); //"Hello...Eddie?"

curried 함수의 ()안 파라미터들을 바꿔 함수를 커스터마이징할 수 있다.

1
2
3
var sayHello = greetDeeplyCurried('Hello')(', ');
sayHello('.')('Heidi'); //"Hello, Heidi."
sayHello('.')('Eddie'); //"Hello, Eddie."
1
2
3
var askHello = sayHello('?');
askHello('Heidi'); //"Hello, Heidi?"
askHello('Eddie'); //"Hello, Eddie?"

🙋🏻‍♀️ 커링을 사용하는 이유가 뭘까?

어떤 함수를 호출할 때 대부분의 매개 변수가 항상 비슷할 때 유용하게 쓸 수 있다. 특히 매우 상세한 사용자 정의 함수를 많이 만들어야 하는 경우 유용하다!

그러나 유일한 문제는 구문. 이러한 curried 함수를 구축할 때 반환된 함수를 계속 중첩하고, 각각 독립된 인수를 포함하는 여러 괄호 집합이 필요한 새 함수를 사용하여 호출해야 하기 때문에 지저분해질 수 있다.

따라서 함수의 이름과 인자만 가지고 사용할 수 있는 currying 함수를 만들어 사용하면 된다. currying 함수는 사용되는 함수의 인자 목록을 추출해, 원본 함수에 커링한다.

1
2
3
4
5
6
7
8
var curryIt = function(uncurried) {
var parameters = Array.prototype.slice.call(arguments,
return function() {
return uncurried.apply(this, parametes.concat(
Array.prototype.slice.call(arguments, 0)
));
};
};

첫 번째 할 일은 지금까지 입력 받은 모든 인자를 복사하는 것이다.

Array#slice 메소드를 이용해 ,arguments의 사본을 parameters라는 변수에 저장한다. 또한 이 추가되는 인자를 parameters에 다시 저장해야 한다.

curryIt() 함수 사용을 위해서는 () 내 함수 이름과 인자를 차례로 넣어 선언해야한다. 빠진 인자는 재 선언하여 출력할 수 있다.

1
2
3
4
5
6
var greeter = function (greeting, separator, emphasis, name) {
console.log(greeting + separator + name + emphasis);
};
var greetHello = curryIt(greeter, 'Hello', ', ', '.');
greetHello('Heidi'); //"Hello, Heidi."
greetHello('Eddie'); //"Hello, Eddie."

원본 curring 함수에서 파생된 함수를 만들 때 인자의 사용은 자유롭다.

1
2
var greetGoodbye = curryIt(greeter, 'Goodbye', ', ');
greetGoodbye('.', 'Joe'); //"Goodbye, Joe."

Getting Serious about Currying

작은 currying 함수는 누락되거나 선택적 매개 변수와 같은 모든 엣지 경우를 처리하지 못할 수도 있겠지만, 인자를 전달하는 구문은 매우 엄격함으로 수행하는데 문제가 없다.

Ramda와 같은 일부 함수형 자바스크립트 라이브러리에는 함수에 필요한 매개 변수를 구분할 수있는 보다 유연한 커링 함수들이 있으며, 개별적으로 또는 그룹으로 전달하여 변형된 커링 함수를 만들 수 있다.

currying을 광범위하게 사용하려면 라이브러리를 사용하는 것이 좋다. 커링 함수에 일관된 네이밍 규칙을 적용하면, 코드 가독성이 높아진다. 함수에서 파생된 또다른 함수들은 작동방식이 매우 명확해야하며, 어떤 인자가 들어오는지 알 수 있어야 한다.

Argument Order

커링 함수에서 인자의 순서는 매우 중요하다. 인수의 순서를 미리 생각하면 커링 함수를 계획하고 적용하기가 더 수월해진다. 앞에 있는 인자일 수록 변동가능성이 적다. 반대로 뒤에 있는 인자일수록 변동가능성이 높다.

결론

Currying은 함수형 자바스크립트에서 매우 유용한 기술이다. 일관성있고 사용하기 쉬운 코드는 이해도도 높기 때문에 커링함수로 작은 라이브러리도 구현해 볼 수 있다. 전체 코드에 currying을 도입함으로 함수 인자 네이밍과 처리하는 방법에 많은 도움이 될 것이다.


📚 참고자료