javascript pipe()函数的类型脚本类型

rqcrx0a6  于 2023-01-16  发布在  Java
关注(0)|答案(4)|浏览(186)

请考虑以下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函数只是将一个操作符的输入通过管道传输到下一个操作符的结果中。
现在考虑运算符类型的新定义:

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

如何重写管道函数以便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)

我想不通,先谢了。

i7uaboj4

i7uaboj41#

下面是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>;

不是很漂亮,但能完成任务。

jdgnovmf

jdgnovmf2#

我知道这不是完全相同的函数签名,但是......我可以建议使用构建器模式吗?
typescript Playground示例

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))
rbl8hiat

rbl8hiat3#

Goblinlord's answer是鼓舞人心的,如果运行时递归是关注的,我们可以类型擦除实际的实现,这样我们就可以用迭代代替递归。类型擦除带来了一个风险,即缺陷可能逃脱编译时类型检查,但我认为这是我愿意付出的代价。

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))
rqmkfv5c

rqmkfv5c4#

代码

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,但是它应该是更精确的类型。但是到目前为止,这是我让代码工作的唯一方法。

  • 请不要打太多,如果它不能正常工作。*

相关问题