람다를 사용한 실용적인 함수형 자바스크립트 - Lee-hyuna/33-js-concepts-kr GitHub Wiki
작성자 : Andrew D'Amelio와 Yuri Takhteyev
rangle.io에서 우리는 한동안 함수형 프로그래밍 스타일의 팬이었으며 많은 프로젝트에서 Underscore와 Lodash를
광범위하게 사용했습니다. 그러나 최근에 Underscore와 매우 유사해 보이지만 다른 라이브러리인 Ramda를 사용하기 시작했습니다. Ramda는 Underscore와 거의 동일한 방법을 제공하지만 제공되는 방식에 따라 기능 구성이 쉬워집니다.
Ramda와 Underscore의 차이점은 currying 과 composition 이라는 두 가지 핵심 개념으로 이어집니다.
Currying
Currying은 여러 개의 매개 변수를 필요로 하는 함수를 적은 수의 매개 변수가 제공 되었을 때
나머지 매개 변수를 기다리는 새로운 함수를 리턴하는 함수로 변환하는 프로세스입니다.
R.multiply(2, 10); // returns 20
여기서 함수를 호출하려면 매개 변수 2와 10을 모두 전달해야 합니다.
var multiplyByTwo = R.multiply(2);
multiplyByTwo(10); // returns 20
아주 깔끔합니다! 우리는 multiplyByTwo라는 새로운 함수를 만들었습니다. 이것은 multiply()에 2를 넣었습니다.
이제 multiplyByTwo 함수에 값을 전달할 수 있습니다.
우리가 이것을 할 수 있는 이유는 람다의 모든 기능이 currying으로 처리되었기 때문입니다.
currying은 오른쪽에서 왼쪽으로 진행됩니다. 일부 인수를 건너뛰면 Ramda는 오른쪽에 있는 인수를 건너 뛴 것으로 가정합니다.
따라서 배열과 함수를 취하는 Ramda함수는 일반적으로 함수를 첫 번째 인수로, 배열을 두 번째 인수로 예상합니다.
이것은 Underscore와 반대입니다.
_.map([1,2,3], _.add(1)) // 2,3,4
비교
R.map(R.add(1), [1,2,3]); // 2,3,4
첫 번째 연산을 오른쪽에서 왼쪽으로 currying으로 조합하면 원하는 것을 명시하고 그 기능을 되찾을 수 있습니다.
그런 다음 실제 데이터를 사용하여 해당 함수를 호출 할 수 있습니다. currying은 쉽고 실용적이게 됩니다.
var addOneToAll = R.map(R.add(1));
addOneToAll([1,2,3]); // returns 2,3,4
이제 다양한 컨텍스트에서 재사용 할 수있는 addOneToAll() 함수가 있습니다.
여기 좀 더 복잡하지만 실용적인 예가 있습니다.
서버에 요청하고 항목의 배열을 가져온 다음 각 항목에서 "cost" 필드를 추출한다고 가정합니다.
Underscore를 사용하여 다음을 수행 할 수 있습니다.
return getItems()
.then(function(items){
return _.pluck(items, 'cost');
});
Ramda를 사용하면 보일러플레이트를 제거 할 수 있습니다.
return getItems()
.then(R.pluck('cost'));
이는 R.pluck('cost')를 호출 할 때 제공된 배열의 각 항목에서 "cost" 필드를 추출하는 함수를 반환하기 때문입니다.
이 함수는 정확히 .then()에 전달하려는 함수입니다.
그러나 currying의 이점을 최대한 활용하려면 Composition과 결합해야합니다.
Composition
수학적으로 말하면, 함수 구성은 함수 f와 g를 취하는 연산으로 g(x) = f(g(x))와 같은 함수 h를 반환합니다.
Ramda는 이를 위해 compose() 함수를 제공합니다.
작고 기능적인 구성 요소에서 더 큰 기능적 동작을 만들 수 있으므로 작성은 currying과 잘 혼합됩니다.
var getCostWithTax = R.compose(
R.multiply(1 + TAX_RATE), // calculate the tax
R.prop('cost') // pull out the 'cost' property
);
이를 통해 객체에서 "cost" 속성을 가져온 다음 결과에 1.13을 곱하는 함수가 제공됩니다.
표준 "구성" 함수는 오른쪽 연관입니다. 즉, 작업이 오른쪽에서 왼쪽으로 진행됩니다.
이 직관적이지 않은 것을 찾으면 R.compose()와 동일하지만 왼쪽에서 오른쪽으로 작동하는 R.pipe()를 사용할 수 있습니다.
var getCostWithTax = R.pipe(
R.prop('cost'), // pull out the 'cost' property
R.multiply(1 + TAX_RATE) // calculate the tax
);
두 가지 함수 만 구성 할 수 있습니다. R.compose 및 R.pipe는 최대 10개의 인수를 사용할 수 있습니다.
currying과 composition은 Underscore와 같은 라이브러리에서 지원한다는 점은 주목할 가치가 있습니다.
그러나 데이터 우선 순위의 매개 변수로 인해 Underscore의 방법이 비현실적이기 때문에 거의 사용되지 않습니다.
Ramda는 실제로 currying과 composition을 쉽게 적용 할 수 있도록 합니다.
처음에 우리는 Ramda와 빨리 사랑에 빠졌습니다.
Ramda의 스타일은 확장과 구성이 가능하며 테스트가 가능하며 선언적인 코드로 연결됩니다.
composition 함수는 매우 자연스럽고 이해하기 쉬운 자바스크립트를 만듭니다.
then...
우리가 Ramda를 점점 더 많이 사용하면서 우리는 약속을 반환하는 비동기 함수가 있을 때 상황이 조금 더 복잡해질 수 있음을 발견했습니다.
var getCostWithTaxAsync = function() {
var getCostWithTax = R.pipe(
R.prop('cost'), // pull out the 'cost' property
R.multiply(1 + TAX_RATE) // multiply it by 1.13
);
return getItem()
.then(getCostWithTax);
}
이것이 Ramda가 없었을때 보다 깨끗하지만 우리가 할 수 있었던 것은 다음과 같습니다.
var getCostWithTaxAsync = R.pipe(
getItem, // get the item
R.prop('cost'), // pull out the 'cost' property
R.multiply(1 + TAX_RATE) // multiply it by 1.13
);
우리가 이것을 할 수 없었던 이유는 getItem()이 promise를 반환하고 R.prop()에 의해 반환 된 함수가
실제 값으로 호출되기를 기대하기 때문입니다.
Promise-Aware Composition
우리는 Ramda 기고자에게 연락하여 약속을 자동으로 풀 수있는 작성 버전을 제안하여
비동기 함수가 실제 결과를 기대하는 함수와 함께 구성 될 수 있도록 했습니다.
긴 토론 후, 우리는 R.pCompose()와 R.pPipe()를 "promise"를 의미하는 "p"와 함께 새로운 함수로 구현하기로 결정했습니다.
R.pPipe를 사용하면 원하는 작업을 정확하게 수행 할 수 있습니다.
var getCostWithTaxAsync = R.pPipe(
getItem, // get a promise for the item
R.prop('cost'), // pull out the 'cost' property
R.multiply(1 + TAX_RATE) // multiply it by 1.13
); // returns a promise for the cost with tax
우리의 코드는 promise를 상당히 많이 사용하는 경향이 있기 때문에,
R.pPipe()는 향후 코드에서 상당히 많은 노력을 기울일 것으로 기대합니다.
시도해보고 의견을 알려주십시오!