파이프란 무엇이며 어떻게 사용합니까?
코드를 작성할 때 하나의 함수에 모든 것을 넣기에는 너무 복잡해지면 코드를 더 우아하게 만들기 위해 무엇을 할 수 있습니까?
해결책은 논리를 분리하기 위해 여러 함수를 만드는 것이지만 더 우아한 방법이 없습니까?
TC39 GitHub의 1단계에서 흥미로운 제안이 이 게시물에서 논의할 주제와 관련이 있습니다.
아직 미완성이지만 읽을 가치가 있는 좋은 제안입니다.
이 게시물에서는 “파이프” 또는 “파이프라인”이라는 함수형 프로그래밍에 사용되는 기술을 소개합니다.
JavaScript에서 파이프 정의 먼저 “파이프“무슨 일인지 설명해드리겠습니다.
“파이프“?”라는 단어를 들었을 때 가장 먼저 떠오르는 것은 무엇입니까? 천장에 연결된 가스관이나 지하에 놓인 수도관을 생각해 본 적이 있을 것입니다.
이러한 파이프는 일반적으로 지점 A에서 지점 B로 리소스를 전송하는 데 사용됩니다.
따라서 단순한 터널처럼 생각할 수 있습니다.
파이프 기능은 실생활에서 사용되는 “파이프”의 아이디어에서 영감을 받았습니다.
파이프는 단방향 통신에 사용됩니다.
각 파이프는 이전 파이프에서 매개변수로 전달된 결과를 사용하여 다른 결과를 생성합니다.
순수 함수 Pipe를 이해하려면 먼저 순수 함수에 대해 알아야 합니다.
순수 함수는 다음 규칙을 따르는 함수입니다.
- 동일한 입력 값에 대해 동일한 반환 값을 보장합니다.
- 함수 범위 밖의 변수 값은 변경되지 않습니다.
그런데 순수 함수가 파이프에 중요한 이유는 무엇입니까?
위에서 언급했듯이 한 파이프에서 반환된 값은 다음 파이프에 입력으로 전달됩니다.
그러나 파이프가 컨텍스트에 따라 다른 값을 반환하면 다음 파이프의 입력 값은 컨텍스트에 따라 변경될 수 있는 불안정한 값입니다.
결과적으로 반환 값도 불안정할 수 있습니다.
파이프를 사용하지 않고
4를 더하고 5를 빼는 두 가지 함수가 있습니다.
모든 함수에서 반환 값을 쉽게 얻을 수 있습니다.
두 함수를 모두 사용하여 단일 반환 값을 얻으려면 다음과 같이 작성할 수 있습니다.
addFour(minusFive(0)); // -1
multiplyByTen(addFour(minusFive(0))); // -10
음수가 마음에 들지 않고 결과의 절대값을 사용하려면 어떻게 해야 합니까?
Math.abs(multiplyByTen(addFour(minusFive(0)))); // 10
이 과정에 대해 어떻게 생각하십니까? 좀 지저분해 보이지 않나요?
특히 마지막에 닫는 괄호를 보면 머리가 핑 돌게 될 수 있습니다.
물론 실행 과정에서 오류는 없었습니다.
그러나 코드를 조금 더 깔끔하게 작성할 수 있다고 생각합니다.
튜브를 만드는 방법
파이프라인을 생성하기 전에 몇 가지 기능이 있어야 합니다.
그리고 Array 클래스 내부에는 Reduce라는 매우 유용한 메서드가 있습니다.
Reduce는 배열을 반복하고 단일 반환 값을 생성합니다.
예를 들어 모든 학생의 총점을 알고 싶다면 다음과 같이 함수를 작성할 수 있습니다.
const scores = (90, 100, 40, 50, 10);
let total = 0;
scores.forEach(score => total += score);
console.log(total); // 290
여기서는 forEach를 사용했습니다.
map 및 filter와 같은 함수는 새 배열을 만들고 반환하기 때문에 이 예제에 적합하지 않습니다.
그러나 이 예제에는 몇 가지 문제가 있습니다.
요소마다 값이 연속적으로 할당되기 때문에 let 키워드로 변수 합계를 선언해야 합니다.
여기서 reduce를 사용하면 결과를 얻을 수 있습니다.
const scores = (90, 100, 40, 50, 10);
const total = scores.reduce((acc, cur) => acc + cur, 0);
console.log(total); // 290
1. 감소 에 필요한 모든 기능을 포함할 수 있습니다.
const pipe = (funcs) => {
return funcs.reduce((acc, cur) => {
...
});
}
2. 역할을 입력합니다.
funcs.reduce((res, func) => {
return func(res);
});
3. 초기값을 설정합니다.
const pipe = (funcs, v) => {
return funcs.reduce((res, func) => {
return func(res);
}, v);
};
이제 이 기능이 제대로 작동하는지 확인해 봅시다.
const addFive = v => v + 5;
const identity = v => v;
console.log(pipe((
addFive,
identity
), 5));
// 10
4. 일반화
단일 함수라도 인수로 받아들이도록 파이프를 수정하는 것이 좋습니다.
이 단계는 나머지 매개변수를 사용합니다.
잔여 매개변수는 함수가 형식 제약 없이 배열의 모든 매개변수를 받는 매개변수입니다.
const pipe = (v, ...funcs) => {
return funcs.reduce((res, func) => {
return func(res);
}, v);
};
const subtract = v => v - 5;
console.log(pipe(10, subtract)); // 5
여기서는 Closer를 사용합니다.
Closer는 이미 실행 중인 외부 함수의 속성에서 참조할 수 있는 내부 함수를 의미합니다.
const pipe = (...funcs) => v => {
return funcs.reduce((res, func) => {
return func(res);
}, v);
};
pipe(add)(5) // 10
마지막으로. 시험
const minusFive = v => v - 5;
const addFour = v => v + 4;
const multiplyByTen = v => v * 10;
const identity = v => v;
const res = pipe(
minusFive,
addFour,
multiplyByTen,
Math.abs,
identity
)(0);
console.log(res); // 10.
파이프는 함수형 프로그래밍에서 매우 유용한 기술입니다.
그는 기능의 복잡성을 줄이고 가독성을 높이는 아주 좋은 사람입니다.
그러나 파이프에 사용되는 모든 것은 순전히 기능적인 상태에서 수행되어야 합니다.
파이프로 함수 구성
이전 부분에서는 함수 배열을 단일 함수로 구성하는 간단한 파이프 함수를 만드는 방법을 배웠습니다.
또한 나머지 매개변수를 사용하여 배열과 개별 함수를 모두 전달함으로써 보다 일반적이고 유연하게 만드는 방법도 살펴보았습니다.
이 부분에서는 파이프를 사용하여 함수를 만드는 몇 가지 고급 기술을 배웁니다.
- 인수를 사용하여 함수 구성
지금까지 파이프 함수는 단일 인수를 사용하는 함수로만 구성되었습니다.
하지만 여러 개의 인자를 받는 함수가 있다면 어떨까요? 스프레드 연산자를 사용하여 이 상황을 처리할 수 있습니다.
예를 들어 다음과 같은 함수가 있다고 합시다.
const add = (a, b) => a + b; const square = a => a * a;
스프레드 연산자를 사용하여 다음과 같은 함수를 구성할 수 있습니다.
const addAndSquare = pipe( ((a, b)) => add(a, b), square );
addAndSquare((2, 3)); // 25
여기서는 두 개의 함수로 구성된 addAndSquare 함수에 두 개의 숫자 배열을 전달합니다.
첫 번째 함수는 두 숫자의 배열을 가져와 함께 더하고 두 번째 함수는 결과를 제곱합니다.
- 비동기 함수 구성
비동기 함수를 만들고 싶다면 어떻게 해야 할까요? async 함수는 값이 아닌 약속을 반환하므로 여기서 reduce를 사용할 수 없습니다.
대신 async/await를 사용해야 합니다.
예를 들어 다음과 같은 비동기 함수가 있다고 가정해 보겠습니다.
const fetchData = async url => {
const response = await fetch(url);
const data = await response.json();
return data;
};
const filterData = async (data, filterFunc) => { return data.filter(filterFunc); };
다음과 같이 함수를 구성할 수 있습니다.
const composedFunc = pipe( fetchData, data =>
filterData(data, item => item.completed),
data => data.map(item => item.title)
);
const url="https://jsonplaceholder.typicode.com/todos";
const filteredData = await composedFunc(url);
여기서는 fetchData 및 filterData 함수를 API에서 데이터를 검색하고 완전히 필터링하고 async/await 구문을 사용하여 제목 배열을 반환하는 단일 함수로 결합합니다.
- Ramda의 파이프 기능 사용
지금까지 처음부터 자체 파이프 기능을 구축했습니다.
그러나 보다 강력하고 기능이 풍부한 파이프 구현을 제공하는 라이브러리가 있습니다.
그러한 라이브러리 중 하나는 Ramda입니다.
Ramda는 데이터 작업에 유용한 여러 기능을 제공하는 JavaScript용 기능적 프로그래밍 라이브러리입니다.
이러한 기능 중 하나는 파이프입니다.
파이프는 사용자 정의 구현과 유사하게 작동하지만 몇 가지 추가 기능이 있습니다.
다음은 Ramda의 파이프 기능을 사용하는 예입니다.
import { pipe, filter, map, prop } from ‘ramda’;
const filterByCompleted = filter(prop('completed'));
const getTitle = map(prop('title'));
const composedFunc = pipe( fetchData, filterByCompleted, getTitle );
const url="https://jsonplaceholder.typicode.com/todos";
const filteredData = await composedFunc(url);
여기서는 Ramda의 Filter, Map 및 Prop 함수를 사용하여 함수를 API에서 데이터를 검색하고 완료 상태로 필터링하고 일련의 제목을 반환하는 단일 함수로 구성합니다.
Ramda의 파이프 기능에는 다음과 같은 많은 추가 기능이 있습니다.
B. 다양한 기능, 자리 표시자 값 등을 지원합니다.
졸업 증서
파이프를 사용하여 함수를 결합하는 것은 코드를 단순화하고 더 모듈화되고 재사용 가능하게 만들 수 있는 강력한 기술입니다.
여러 인수를 사용하여 함수를 처리하고 구성하는 것과 같은 고급 기술을 사용할 수 있습니다.