TypeScript에서 입력한 "compose"함수 만들기
함수식 프로그래밍의 핵심 개념 중 하나는 조합이다. 여러 함수를 하나의 함수로 조합하여 모든 작업을 수행할 것이다.한 함수의 출력은 다른 함수의 입력에 전달된다.많은 라이브러리는 이 점을 실현하는 데 도움을 주는
compose
함수를 포함하고 있으며, 로다쉬와Ramda를 포함하여,React에서 자주 사용하는 모델이기도 하다.이러한 도움말을 사용하면 다음과 같이 열 수 있습니다.const output = fn1(fn2(fn3(fn4(input))));
다음을 수행합니다.const composed = compose(fn1, fn2, fn3, fn4);
const output = composed(input);
그리고 필요에 따라 새로운 composed
함수를 다시 사용할 수 있습니다.compose
의 파트너는 pipe
으로 반전된 매개 변수로만 구성됩니다.이 경우 중첩된 디스플레이 순서가 아니라 매개변수의 실행 순서입니다.위의 조합 예시에서 먼저 내부 fn4
함수를 호출한 다음에 그 출력을 fn3
에 전달한 다음에 fn2
과 fn1
에 전달한다.많은 상황에서 더욱 직관적인 방법은 함수를 유닉스 파이프로 상상하여 값을 왼쪽으로 전달한 다음에 모든 함수에서 다음 함수로 전달하는 것이다.허구의 예로서 디렉터리에서 가장 큰 파일을 찾는 셸 명령은 다음과 같습니다.du -s * | sort -n | tail
가상화된 JavaScript 환경에서const largest = tail(sort(du("*")));
파이핑을 사용하여 다음을 수행할 수 있습니다.const findLargest = pipe(du, sort, tail);
const largest = findLargest("*");
더 현실적인 예에서 JSON 파일을 불러와서 몇 가지 조작을 하고 저장하는 것을 상상해 보세요.가장 좋은 것은 진실한 예이다. 나는 이 점을 실현했다. 왜냐하면 나는 알렉사 테스트 기술을 연구하고 있기 때문이다. 거기에서 나는 기능성 방법을 사용하여 요청을 처리한다.나는 일련의 처리 프로그램을 통해 요청과 응답 데이터를 포함하는 대상을 전달했다. 이 처리 프로그램들은 사용자가 질문에 대답했는지 확인한 다음에 다음 질문을 하고 적절하면 게임을 완성한다.
const handler = pipe(handleAnswer, askQuestion, completeGame);
const params = handler(initialParams);
이를 위해, 나는 파라미터를 정의하고, 파라미터를 입력할 수 있기를 바란다.handler
의 서명이 전송된 함수의 서명과 일치하기를 바랍니다.또한 TypeScript가 들어오는 모든 함수가 동일한 유형인지 확인하기를 바랍니다.나는 또 그것이 이 점을 추단할 수 있기를 바란다. 왜냐하면 불필요하게 유형을 지정하는 것은 매우 번거롭기 때문이다.임의의 수량 매개 변수를 지원하는 TypeScript 라이브러리를 찾을 수 없기 때문에 계속 작성합시다.우선, 우리는 실제 함수를 작성한 후에 그것들을 어떻게 입력하는지 확인합시다.내장된
Array.reduce
을 사용하면 간단합니다.우리는 파라미터 순서를 변경할 필요가 없기 때문에 pipe
부터 시작할 것이다.reduce
의 가장 간단한 서명을 기억하겠습니다.function reduce(callbackfn: (previousValue: T, currentValue: T) => T): T;
reduce
은 리셋 함수로 전달되며, 이 함수는 그룹의 모든 요소로 호출됩니다.리셋 함수에 전달되는 첫 번째 매개 변수는 이전 리셋 함수의 리셋 값입니다.두 번째 매개 변수는 그룹의 다음 값입니다.Reduce의 짧은 버전에 대해 첫 번째 리셋 호출은 실제 전달 그룹의 첫 번째 요소는 previousValue
이고 두 번째 요소는 currentValue
이다.초기 값을 전달할 수 있는 더 긴 버전이 있습니다. 반환 값이 그룹 요소의 유형과 다르면 이렇게 해야 합니다.우리는 더욱 간단한 버전부터 시작할 것이다.export const pipe = (...fns) =>
fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)));
이것은 점차적으로 조합 함수를 만들어서 교체할 때 수조에 다음 함수를 추가합니다.새 함수를 되돌려줍니다. 이 함수는 prevFn
(이 함수는 수조의 이전 함수로 구성됨)을 순서대로 호출한 다음 nextFn
에 대한 호출에 포장합니다.호출할 때마다 함수를 다음 함수에 포장하고 마지막 함수 호출 그룹의 모든 요소를 호출합니다.export const pipe = <R>(...fns: Array<(a: R) => R>) =>
fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)));
이것은 상당히 곤혹스러워 보이지만, 보기에는 그렇게 나쁘지 않다.<R>
은 함수 반환 유형의 자리 표시자입니다.이것은 범용 함수이지만, 흥미로운 것은 TypeScript가 전달하는 매개 변수 형식에서 이러한 유형을 추정할 수 있다는 것이다. 문자열을 전달하면 문자열이 되돌아온다는 것을 안다.서명은 pipe
이 임의의 수량의 매개 변수를 받아들인다는 것을 나타낸다. 이 매개 변수는 모두 하나의 매개 변수를 받아들여 이 매개 변수와 같은 유형의 값을 되돌려주는 함수이다.그러나 이것은 완전히 정확하지 않다. pipe
은 적어도 하나의 매개 변수가 필요하다.첫 번째 인자를 표시하기 위해 서명을 변경해야 합니다.이를 위해, 우리는 같은 유형의 초기 매개 변수를 추가한 다음, 그것을 두 번째 매개 변수로 reduce
에 전달한다. 이것은 그것이 시작 값으로 사용된다는 것을 의미한다.export const pipe = <R>(fn1: (a: R) => R, ...fns: Array<(a: R) => R>) =>
fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)), fn1);
이제 pipe
을 정의했습니다. compose
을 정의하는 것은 nextFn
과 prevFn
을 전환하는 순서처럼 간단합니다.export const compose = <R>(fn1: (a: R) => R, ...fns: Array<(a: R) => R>) =>
fns.reduce((prevFn, nextFn) => value => prevFn(nextFn(value)), fn1);
진일보한 연구를 하기 전에, 우리는 그것이 예상대로 작동하는지 테스트해야 한다.나는 Jest을 사용하여 테스트를 하는 것을 좋아하기 때문에 테스트를 정의하고 어떻게 해야 하는지 봅시다.import { compose, pipe } from "./utils";
describe("Functional utils", () => {
it("composes functions", () => {
const fn1 = (val: string) => `fn1(${val})`;
const fn2 = (val: string) => `fn2(${val})`;
const fn3 = (val: string) => `fn3(${val})`;
const composedFunction = compose(fn1, fn2, fn3);
expect(composedFunction("inner")).toBe("fn1(fn2(fn3(inner)))");
});
it("pipes functions", () => {
const fn1 = (val: string) => `fn1(${val})`;
const fn2 = (val: string) => `fn2(${val})`;
const fn3 = (val: string) => `fn3(${val})`;
const pipedFunction = pipe(fn1, fn2, fn3);
expect(pipedFunction("inner")).toBe("fn3(fn2(fn1(inner)))");
});
});
이 함수들은 문자열만 되돌려줍니다. 호출되었음을 나타냅니다.만약 당신이backtick 문법을 모른다면, 그들은 템플릿 문자를 사용합니다.조합 함수의 유형을 보고, 어떻게 작동하는지 보고, 함수의 서명을 변경하고, 유형 검사가 유효한지 확인하십시오.여기서
pipedFunction
의 유형은 pipe
에 전달된 함수 유형에서 추정된 것을 볼 수 있습니다.여기에서
fn2
을 예상 숫자로 변경하면 형식 오류가 발생할 수 있음을 알 수 있습니다. 함수는 모두 같은 서명을 해야 합니다.만약 우리가 여러 개의 매개 변수를 받아들이는 함수를 전달했다면, 우리는 유사한 오류를 얻을 것이다.그러나 이것은
compose
또는 pipe
함수의 제한이 될 필요가 없다.엄밀히 말하면, 첫 번째 함수는 같은 유형을 되돌려주고, 모든 다른 함수는 매개 변수를 받아들일 수 있다.우리는 다음과 같은 몇 가지를 할 수 있어야 한다.it("pipes functions with different initial type", () => {
const fn1 = (val: string, num: number) => `fn1(${val}-${num})`;
const fn2 = (val: string) => `fn2(${val})`;
const fn3 = (val: string) => `fn3(${val})`;
const pipedFunction = pipe(fn1, fn2, fn3);
expect(pipedFunction("inner", 2)).toBe("fn3(fn2(fn1(inner-2)))");
});
조합이나 파이프 함수는 첫 번째 함수와 같은 서명을 해야 한다.그러면 우리는 어떻게 입력해야 합니까?우리는 fn1
의 유형을 변경하여 서로 다른 파라미터를 허용할 수 있지만, 이렇게 하면 우리는 이러한 파라미터의 유형 안전성을 잃게 된다.export const pipe = <R>(
fn1: (...args: any[]) => R,
...fns: Array<(a: R) => R>
) => fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)), fn1);
fn1
의 매개 변수를 나타내는 또 다른 범용 유형이 필요합니다.TypeScript 3 이전에는 다음과 같은 로드를 추가할 수 있습니다.export function pipe<T1, R>(
fn1: (arg1: T1) => R,
...fns: Array<(a: R) => R>
): (arg1: T1) => R;
export function pipe<T1, T2, R>(
fn1: (arg1: T1, arg2: T2) => R,
...fns: Array<(a: R) => R>
): (arg1: T1, arg2: T2) => R;
export function pipe<T1, T2, T3, R>(
fn1: (arg1: T1, arg2: T2, arg3: T3) => R,
...fns: Array<(a: R) => R>
): (arg1: T1, arg2: T2, arg3: T3) => R;
export function pipe<T1, T2, T3, T4, R>(
fn1: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => R,
...fns: Array<(a: R) => R>
): (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => R;
export function pipe<R>(
fn1: (...args: any[]) => R,
...fns: Array<(a: R) => R>
): (a: R) => R {
return fns.reduce((prevFn, nextFn) => value => nextFn(prevFn(value)), fn1);
}
이것은 분명히 터무니없는 것이다.다행히도 TypeScript3는 typed ...rest
parameters을 도입했다.이를 통해 다음과 같은 모든 매개 변수에 적용할 수 있습니다.export const pipe = <T extends any[], R>(
fn1: (...args: T) => R,
...fns: Array<(a: R) => R>
) => {
const piped = fns.reduce(
(prevFn, nextFn) => (value: R) => nextFn(prevFn(value)),
value => value
);
return (...args: T) => piped(fn1(...args));
};
우리는 첫 번째 함수의 매개 변수를 나타내는 새로운 범용 유형 T
을 추가했다.TypeScript 3 이전에 이 코드가 오류를 냈지만, 지금은 extends any[]
이라고 선언하여 컴파일러가 형식화된 모듈 매개 변수 목록으로 받아들입니다.우리는 예전처럼 fn1
을 통해 직접 줄일 수 없다. 왜냐하면 지금은 다른 유형이기 때문이다.반대로 우리는 표지 함수 value => value
을 두 번째 값으로 전달한다. - 매개 변수만 되돌아오는 함수는 변하지 않는다.그리고 우리는 정확한 유형으로 간소화된 함수를 다른 함수에 포장하고 이 함수를 되돌려줍니다.첫 번째 매개변수 유형과 동일한 파이프 함수를 제공합니다.
다른 매개 변수의 형식을 검사합니다. 첫 번째 함수를 받아들여서 같은 종류의 매개 변수를 되돌려야 하고, 같은 종류를 되돌려야 합니다.
그럼 이게
compose
에 뭘 남겨줄까요?불행하게도, 우리는 같은 방식으로 그것을 입력할 수 없다.pipe
은 첫 번째 함수에서 그 유형을 가져오고, compose
은 마지막 함수의 유형을 사용합니다.매개 변수 목록의 시작 부분에 ...rest
개의 매개 변수가 필요한 내용을 입력하십시오. 현재 지원되지 않습니다.고정 변수 매개 변수 함수 중 마지막 매개 변수
#1360
JeroMiya
에 발표
이 문제에서 이 건의를 추출합니다.
https://github.com/Microsoft/TypeScript/issues/1336
현재 변수 매개 변수 목록은 변수 매개 변수를 함수의 마지막 매개 변수로만 지원합니다.function foo(arg1: number, ...arg2: string[]) {
}
This compiles to the following javascript:
function foo(arg1) {
var arg2 = [];
for (var _i = 1; _i < arguments.length; _i++) {
arg2[_i - 1] = arguments[_i];
}
}
However, variable argument functions are limited to appearing only as the last argument and not the first argument. I propose support be added for having a variable argument appear first, followed by one or more fixed arguments:
function subscribe(...events: string[], callback: (message: string) => void) {
}
// the following would compile
subscribe(message => alert(message)); // gets all messages
subscribe('errorMessages', message => alert(message));
subscribe(
'errorMessages',
'customMessageTypeFoo123',
(message: string) => {
alert(message);
});
// the following would not compile
subscribe(); // supplied parameters do not match any signature of call target
subscribe('a1'); // argument of type 'string' does not match parameter of type '(message: string) => void'
subscribe('a1', 'a2'); // argument of type 'string' does not match parameter of type '(message: string) => void'
subscribe compiles to the following JavaScript:
function subscribe() {
var events= [];
var callback = arguments[arguments.length - 1];
for(var _i = 0; _i < arguments.length - 2; _i++) {
events[_i] = arguments[_i];
}
}
notes: it should be impossible for typescript code to call this function with zero arguments when typechecking. If JS or untyped TS code calls it without arguments, callback will be undefined. However, the same is true of fixed arguments at the beginning of the function.
edit: used a more realistic/motivating example for the fixed-last/variable-arguments-first function.
Until then you'll need to stick with composing functions that accept just one argument.
The final library is here:
export const pipe = <T extends any[], R>(
fn1: (...args: T) => R,
...fns: Array<(a: R) => R>
) => {
const piped = fns.reduce(
(prevFn, nextFn) => (value: R) => nextFn(prevFn(value)),
value => value
);
return (...args: T) => piped(fn1(...args));
};
export const compose = <R>(fn1: (a: R) => R, ...fns: Array<(a: R) => R>) =>
fns.reduce((prevFn, nextFn) => value => prevFn(nextFn(value)), fn1);
이 테스트는 사용 상황을 표시합니다.import { compose, pipe } from "./utils";
describe("Functional helpers", () => {
it("composes functions", () => {
const fn1 = (val: string) => `fn1(${val})`;
const fn2 = (val: string) => `fn2(${val})`;
const fn3 = (val: string) => `fn3(${val})`;
const composedFunction = compose(fn1, fn2, fn3);
expect(composedFunction("inner")).toBe("fn1(fn2(fn3(inner)))");
});
it("pipes functions", () => {
const fn1 = (val: string) => `fn1(${val})`;
const fn2 = (val: string) => `fn2(${val})`;
const fn3 = (val: string) => `fn3(${val})`;
const pipedFunction = pipe(fn1, fn2, fn3);
expect(pipedFunction("inner")).toBe("fn3(fn2(fn1(inner)))");
});
it("pipes functions with different initial type", () => {
const fn1 = (val: string, num: number) => `fn1(${val}-${num})`;
const fn2 = (val: string) => `fn2(${val})`;
const fn3 = (val: string) => `fn3(${val})`;
const pipedFunction = pipe(fn1, fn2, fn3);
expect(pipedFunction("inner", 2)).toBe("fn3(fn2(fn1(inner-2)))");
});
});
Reference
이 문제에 관하여(TypeScript에서 입력한 "compose"함수 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/ascorbic/creating-a-typed-compose-function-in-typescript-3-351i텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)