簡體   English   中英

Typescript 管道類型() function

[英]Typescript types for a pipe() function

考慮以下 TypeScript 代碼:

type operator<T> = (input:T) => T

const pipe = <T>(...operators:operator<T>[]) => (input:T):T => operators.reduce((output, f) => f(output), input)

const add2:operator<number> = x => x+2

const times3:operator<number> = x => x*3

console.log(pipe(add2, times3)(1))    //output 9

pipe function 只是將一個運算符的輸入通過管道傳遞給下一個運算符的結果。

現在考慮運算符類型的這個新定義:

type operator<T, U> = (input:T) => U

應該如何重寫 pipe function以便 IDE 讓我知道我是否正確使用了這些類型?

例如:考慮這兩個運算符:

const times3:operator<number, number> = x => x*3

const toStr:operator<number, string> = x => `${x}`

我希望它能正常工作:

pipe(times3, toStr)(1)

在這里,我希望 IDE 警告我類型錯誤:

pipe(toStr, times3)(1)

我無法弄清楚這一點,在此先感謝。

以下是RxJS是如何做到的:

pipe(): Observable<T>;
pipe<A>(op1: OperatorFunction<T, A>): Observable<A>;
pipe<A, B>(op1: OperatorFunction<T, A>, op2: OperatorFunction<A, B>): Observable<B>;
pipe<A, B, C>(op1: OperatorFunction<T, A>, op2: OperatorFunction<A, B>, op3: OperatorFunction<B, C>): Observable<C>;
pipe<A, B, C, D>(
    op1: OperatorFunction<T, A>,
    op2: OperatorFunction<A, B>,
    op3: OperatorFunction<B, C>,
    op4: OperatorFunction<C, D>
): Observable<D>;
pipe<A, B, C, D, E>(
    op1: OperatorFunction<T, A>,
    op2: OperatorFunction<A, B>,
    op3: OperatorFunction<B, C>,
    op4: OperatorFunction<C, D>,
    op5: OperatorFunction<D, E>
): Observable<E>;
pipe<A, B, C, D, E, F>(
    op1: OperatorFunction<T, A>,
    op2: OperatorFunction<A, B>,
    op3: OperatorFunction<B, C>,
    op4: OperatorFunction<C, D>,
    op5: OperatorFunction<D, E>,
    op6: OperatorFunction<E, F>
): Observable<F>;
pipe<A, B, C, D, E, F, G>(
    op1: OperatorFunction<T, A>,
    op2: OperatorFunction<A, B>,
    op3: OperatorFunction<B, C>,
    op4: OperatorFunction<C, D>,
    op5: OperatorFunction<D, E>,
    op6: OperatorFunction<E, F>,
    op7: OperatorFunction<F, G>
): Observable<G>;
pipe<A, B, C, D, E, F, G, H>(
    op1: OperatorFunction<T, A>,
    op2: OperatorFunction<A, B>,
    op3: OperatorFunction<B, C>,
    op4: OperatorFunction<C, D>,
    op5: OperatorFunction<D, E>,
    op6: OperatorFunction<E, F>,
    op7: OperatorFunction<F, G>,
    op8: OperatorFunction<G, H>
): Observable<H>;
pipe<A, B, C, D, E, F, G, H, I>(
    op1: OperatorFunction<T, A>,
    op2: OperatorFunction<A, B>,
    op3: OperatorFunction<B, C>,
    op4: OperatorFunction<C, D>,
    op5: OperatorFunction<D, E>,
    op6: OperatorFunction<E, F>,
    op7: OperatorFunction<F, G>,
    op8: OperatorFunction<G, H>,
    op9: OperatorFunction<H, I>
): Observable<I>;
pipe<A, B, C, D, E, F, G, H, I>(
    op1: OperatorFunction<T, A>,
    op2: OperatorFunction<A, B>,
    op3: OperatorFunction<B, C>,
    op4: OperatorFunction<C, D>,
    op5: OperatorFunction<D, E>,
    op6: OperatorFunction<E, F>,
    op7: OperatorFunction<F, G>,
    op8: OperatorFunction<G, H>,
    op9: OperatorFunction<H, I>,
    ...operations: OperatorFunction<any, any>[]
): Observable<unknown>;

它不漂亮,但它完成了工作。

我知道它不是完全相同的 function 簽名但是......我可以建議使用構建器模式嗎?

Typescript 游樂場示例

 const pipe = <A, B>(fn: (a: A) => B) => { return { f: function<C>(g: (x: B) => C) { return pipe((arg: A) => g(fn(arg)))}, build: () => fn } } const compose = <A, B>(fn: (a: A) => B) => { return { f: function<C>(g: (x: C) => A) { return compose((arg: C) => fn(g(arg)))}, build: () => fn } } const add = (x: number) => (y: number) => x + y const format = (n: number) => `value: ${n.toString()}` const upper = (s: string) => s.toUpperCase() const process = pipe(add(2)).f(add(6)).f(format).f(upper).build() const process2 = compose(upper).f(format).f(add(6)).f(add(5)).build() console.log(process(6)) console.log(process2(6))

Goblinlord 的回答很有啟發性,如果關注的是運行時遞歸,我們可以鍵入擦除實際實現,以便我們可以用迭代替換遞歸。 類型擦除帶來了缺陷可能逃脫編譯時類型檢查的風險,但我認為這是我願意付出的代價。

 type Fn<T, U> = (i: T) => U type Pipe<T, U> = { f: <K>(fn: Fn<U, K>) => Pipe<T, K>, build: () => Fn<T, U> } function pipe<T, U>(fn: Fn<T, U>): Pipe<T, U> { const fns: Fn<any, any>[] = [fn] const p: Pipe<any, any> = { f: (fn) => { fns.push(fn); return p; }, build: () => { return (input) => fns.reduce((prev, curr) => curr(prev), input); } } return p; } const add = (x: number) => (y: number) => x + y const format = (n: number) => `value: ${n.toString()}` const upper = (s: string) => s.toUpperCase() const process = pipe(add(2)).f(add(6)).f(format).f(upper).build() console.log(process(1))

靈感來自GoblinlordmeritonArray Sort的這個解決方案

代碼

type Reverse<Arr extends readonly any[]> = Arr extends [infer TFirst, ...infer TRest] ? [...Reverse<TRest>, TFirst] : Arr;

type Operator<A, B> = (value: A) => B
type OperatorA<T> = T extends Operator<infer A, any> ? A : never
type OperatorB<T> = T extends Operator<any, infer B> ? B : never

type PipeOperators<Operators extends unknown[], Input> =
  Operators extends [infer Item, ...infer Tail] ? (
    [
      Operator<Input, OperatorB<Item>>,
      ...PipeOperators<Tail, OperatorB<Item>>
    ]
  ) : Operators
type PipeOperatorsOutput<Operators extends unknown[]> = OperatorB<Reverse<Operators>[0]>

function pipe<Input, Operators extends unknown[]>(...operators: PipeOperators<Operators, Input>): (input: Input) => PipeOperatorsOutput<Operators> {
  return operators as never // Runtime implementation.
}



const add = (x: number) => (y: number) => x + y
const format = (n: number) => `value: ${n.toString()}`
const upper = (s: string) => s.toUpperCase()


const __TEST1__: string = pipe(add(2), format, upper)(1)
const __TEST2__: string = pipe(add(2), upper)(1) // Error: Type 'number' is not assignable to type 'string'.
const __TEST3__: string = pipe(add(2), format)("") // Error: Argument of type 'string' is not assignable to parameter of type 'number'.
const __TEST4__: string = pipe(add(2), format)(1)
const __TEST5__: number = pipe(add(2), add(2))(1)

有些地方我使用了anyunknown ,但它應該是更精確的類型。 但到目前為止,這是我讓代碼正常工作的唯一方法。

如果它不能正常工作,請不要打太多。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM