티스토리 뷰
currying, partial application
자바스크립트를 최대한 짧고 적은 줄에서 반복적인 함수를 호출하기위한 방법
currying의 장점
- 함수의 인수(파라메터)를 부분적으로 적용할 수 있는 함수를 생성할 수 있다
- 함수안에 기능적으로 분리된 인수를 전달하게 되므로써 기능을 분리하여
그 결과를 얻을 수 있다
( 좀더 자세히 : 기능별로 나누어서, 쪼개서 호출할 수 있다 )
- 임수의 부분(혹은 집합)을 전달하고 나머지 인수가 실행되는 함수를 리턴받을수 있다
- (개인적으로) 객체지향 언어에서 의례적으로 .(dot)을 참조하였던 방식대신
연속적인 함수호출을 통하여 chaining 패턴같이 이어나갈수 있는 방식으로 느껴진다
자바스크립트에서의 currying
Haskell 및 Scala와 같은 언어에서 기본요소로 포함되어있지만
자바스크립트에서는 내장되어있지 않다
자바스크립트는 문법이 매우 유연하기 때문에 기능적 트릭을 이용하여
커링작업을 할 수있다
안녕하세요? 인사를 하는 함수 작업
누군가에게 인사하는 함수를 상상해보자
우리는 모두 이름과 인사말을 취하는 간단한 인사말 기능을 만드는 법을 알고있는데
이름 + 인사말을 콘솔에 기록하는 함수를 만든다면 아래와 같을 것이다
1 2 3 4 | var greet = function(greeting, name) { console.log(greeting + ", " + name); }; greet("Hello", "Heidi"); //"Hello, Heidi" | cs |
위의 함수는 제대로 작동하기 위해서 이름과 인사말을 인수로 전달해야 한다.
currying 첫걸음
1 2 3 4 5 | var greetCurried = function(greeting) { return function(name) { console.log(greeting + ", " + name); }; }; | cs |
인사함수를 간단히 중첩된 currying을 사용하여 함수를 다시 작성해 보았다.
함수를 살펴보면 기본함수는 greeting 부분만 필요하고
greet 하고자 하는 사람의 이름을 호출하는 다른함수를 반환하게 된다
이렇게 바뀐 함수를 통해 모든 유형의 인사말에 대해 새로운 함수를 만들 수
(정확히는 리턴받게된 기능을 통하여 제공..?)
있게되므로 새 함수에 인사 할 사람의 이름을 전달할 수 있다
1 2 3 | var greetHello = greetCurried("Hello"); greetHello("Heidi"); //"Hello, Heidi" greetHello("Eddie"); //"Hello, Eddie" | cs |
여기서 끝이 아니다!
당연하게도 원래의 커링을 직접호출 할 수 있다(이게 핵심)
각각의 매개 변수를 별도의 괄호 세트에 하나씩 전달하는 것으로 바로 호출 할 수 있다
1 | greetCurried("Hi there")("Howard"); //"Hi there, Howard" | cs |
< 그림이다 >
인사하는 커링함수의 기능을 확장하기
우리는 기존함수에서 약간의 접근방식을 조정하여 인수를 다루는법을 배웠다
원하는 만큼 많은 인수를 사용하여 기능을 확장해 보자
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); }; }; }; }; | cs |
먼저, 5번 라인의 name이 console.log 3번째에 print됨을 유의하고
총 4가지 기능이 추가되었지만 얼마나 중첩되었는지와는 무관하게
여러가지 방식으로 많은 사람들을 선택할 수 있도록
새로운 사용자 지정 함수를 만들었다.
1 2 3 | var greetAwkwardly = greetDeeplyCurried("Hello")("...")("?"); greetAwkwardly("Heidi"); //"Hello...Heidi?" greetAwkwardly("Eddie"); //"Hello...Eddie?" | cs |
기존의 방법대로라면 ("Hello", "....", "Heidi", "?"), ("Hello", "....", "Eddie", "?")... 으로
사용 했을 텐데 emphasis 까지만 호출하여 기능return function(name)을 받아
각각 Heidi와 Eddie에게 인사를 건넷다!
여기서 끝이 아니라
아래처럼 각 매개변수는 () 를 통하여 개별적, 기능적으로 끊어서
전달되어 사용할 수 있다
1 2 3 | var sayHello = greetDeeplyCurried("Hello")(", "); sayHello(".")("Heidi"); //"Hello, Heidi." sayHello(".")("Eddie"); //"Hello, Eddie." | cs |
1 2 3 | var askHello = sayHello("?"); askHello("Heidi"); //"Hello, Heidi?" askHello("Eddie"); //"Hello, Eddie?" | cs |
더많고 상세한 Currying을 사용하기 위한 방법!
매우 상세한 사용자 정의 함수를 많이 작성해야 하는 경우는 어떨까?
이러한 커링 함수를 빌드 할 때, 리턴된 함수가 계속 중첩됨을 유지해야 하는데..
더군다나 각각 별도의 독립된 인수가 들어있는 여러세트의 괄호를 필요로 하는
새 함수를 호출해야 하므로 가독성이 떨어지고 난잡해 보일 수 있다.
이 문제를 해결하기 위한 한 가지 방법은
반환(return)없이 작성된 함수를 currying 함수로 삼고,
이 currying된 함수는 인수 목록(arguments)를 추출하여
리턴하는데 사용하면 된다.
1 2 3 4 5 6 7 8 | var curryIt = function(uncurried) { var parameters = Array.prototype.slice.call(arguments, 1); return function() { return uncurried.apply(this, parameters.concat( Array.prototype.slice.call(arguments, 0) )); }; }; | cs |
이렇게 currying을 위한 함수를 만들어 주고 여기에는 return 없는 함수이자
많은 인수를 취하게 되는 함수를 넘기면 된다.
우리가 얻게되는 것은 나머지 인수들인데 아래와 같이 사용하면 된다
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." | cs |
curryIt 함수의 장점은 사용하려는 인수의 개수에 제한이 없어서
아래와 같이 인수를 조정해서 사용해도 된다.
1 2 | var greetGoodbye = curryIt(greeter, "Goodbye", ", "); greetGoodbye(".", "Joe"); //"Goodbye, Joe." | cs |
Currying 라이브러리 Ramda
Ramda 와 같은 자바스크립트 라이브러리에서는 함수에 필요한 매개변수를 구분할 수
있는 보다 유연한 커링함수가 존재한다.
개별적 혹은 그룹으로 전달하여 커스텀 커링으로 변형하여 사용할 수 있는 장점이 있고
커링을 광범위하게 사용하려면 이 라이브러리를 추천한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import * as R from 'ramda' // curry 메서드 const addFourNumbers = (a, b, c, d) => a + b + c + d; const curriedAddFourNumbers = R.curry(addFourNumbers); const f = curriedAddFourNumbers(1, 2); const g = f(3); g(4); //=> 10 // curryN const sumArgs = (...args) => R.sum(args); const curriedAddFourNumbers = R.curryN(4, sumArgs); const f = curriedAddFourNumbers(1, 2); const g = f(3); g(4); //=> 10 | cs |
이 라이브러리 역시 커링 이기에 아래와 같이 분기별 호출이 가능하다.
g(1)(2)(3)
g(1)(2, 3)
g(1, 2)(3)
g(1, 2, 3)
두 번째로 특수한 자리 표시 자 값을 R.__사용하여 "간격"을 지정하면
위치에 관계없이 인수 조합을 부분적으로 적용 할 수있다.
이경우 다음과 같이 언더바 _ 를 통해서 사용한다 (R.__, 도 같은 표현)
g(1, 2, 3)
g(_, 2, 3)(1)
g(_, _, 3)(1)(2)
g(_, _, 3)(1, 2)
g(_, 2)(1)(3)
g(_, 2)(1, 3)
g(_, 2)(_, 3)(1)
실무에서 currying은 어떠한가
위에서 확장한 커링 함수로는 인자가 누락되거나 선택적인 인수와 같은 예외처리를
일일이 구현해 주어야 할 수도있다.
그렇지만 인수의 패턴이 동일하여 엄격한 상태를 유지하는 한
합리적으로 작업이 수행될 가능성이 높다.
커링에 중첩된 괄호를 사용하고 싶은 경우, 혹은 더 강력한 전달 함수를
포함하는 경우에 커링 함수에는 일관된 명명 규칙을 사용해야 코드를 쉽게
읽을 수 있다
함수의 파생된 각 변형에는 동작 방식이 명확하고 어떠한 인수가 예상되는지를
나타내는 이름이 있어야 한다.
그래서 결론
커링의 중요한 포인트는 '처리의 흐름' 이라고 표현하고 싶다
앞에서 설명하였던 방법을 사용하면 분명 원래 함수에 전달 된 마지막 인수가 되도록
한 이유가 여기에 있다 ( 아까 인사하는 커링함수의 기능을 확장하기 - 에서
"먼저, 5번 라인의 name이 console.log 3번째에 print됨을 유의하고") 가 그 이유..
대체 할 가능성이 큰 인수를 마지막으로 배치한 이유이다.
인수의 순서에 대해서 미리 생각하고 설계하면 작업에 쉽게 적용 할 수 있고
가장 자주 변할 가능성이 있는 기능일 수록 순서를 고려하여 설계하면
자바스크립트에서 매우 유용한 기능이라 할 수 있다
커링은 일관성 있게 작동하고 사용하기(작성이라고는 안했다) 쉬운 함수라
요약할 수 있으며 코드 전체에 부분적으로 적용된 함수 혹은 잠재적인 반복성을
가지고 있는 함수를 방지하여
인수의 명명을 통해 처리하는 습관으로 개선시킬 수 있는 유용한 기술이다.
개념및 도움이 될만한 참조 문서
https://www.sitepoint.com/currying-in-functional-javascript/
(가장 쉽고 친절하게 설명되어 분문에 인용)
https://medium.com/@kbrainwave/currying-in-javascript-ce6da2d324fe
'■ 프론트엔드 ■ > JavaScript' 카테고리의 다른 글
es6, this and bind (0) | 2020.03.04 |
---|---|
ES6 Immediately-invoked function expression (0) | 2019.03.14 |
new 연산자를 피하라 (1) | 2018.01.26 |
2017.07 자바스크립트 프레임워크 트렌드차트 (0) | 2017.07.06 |
rgb를 16 진수로 (0) | 2017.03.20 |