如何保护curried函数中参数的顺序



我不想过多地讨论细节以保持简洁,但我有一个名为IsOrdered<[1,2,4]>的类型,当数字确实在上升时,它解析为true,对于其他所有type f = IsOrdered<[3,2]> // false,它解析成false。它正常工作。

现在我有以下代码不起作用。要点是函数storm(...pipes),其中管道是类型Pipe<number>的元素。编号用于在类型级别上标记管道,并使用该编号验证其顺序。

我想要实现的是storm()将拒绝接受未正确排序的管道。正如您在代码中看到的,FilterPipe<0>OrderByPipe<4>

因此可以调用storm(filter(), orderBy()),但不能调用storm(orderBy(), filter())。然而,类型解析仅在我的storm()调用之外工作,如测试部分所示。

type TypeEqual<T, U> = T extends U ? (U extends T ? true : false) : false
type Reverse<Tuple extends any[]> = Tuple extends [infer Head, ...infer Rest] ? [...Reverse<Rest>, Head] : []
type Nat = 0 | { suc: Nat }
type Nats = {
0: 0
1: { suc: Nats[0] }
2: { suc: Nats[1] }
3: { suc: Nats[2] }
4: { suc: Nats[3] }
5: { suc: Nats[4] }
6: { suc: Nats[5] }
7: { suc: Nats[7] }
8: { suc: Nats[8] }
}
type GT<T1 extends Nat, T2 extends Nat> = TypeEqual<T1, T2> extends true
? false
: T2 extends 0
? true
: T1 extends { suc: infer T3 }
? T2 extends { suc: infer T4 }
? T3 extends Nat
? T4 extends Nat
? GT<T3, T4>
: never
: never
: never
: false
type AnyGT<T1, T2> = T1 extends keyof Nats ? (T2 extends keyof Nats ? GT<Nats[T1], Nats[T2]> : false) : false
type IsDesc<T extends any[]> = T extends [] ? true : T extends [infer _] ? true : T extends [infer T1, infer T2, ...infer Ts] ? (AnyGT<T1, T2> extends true ? IsDesc<[T2, ...Ts]> : false) : false
type IsOrdered$<T extends any[]> = IsDesc<T> extends true ? true : IsDesc<T>
type IsOrdered<T extends any[]> = IsOrdered$<Reverse<T>>

// ^ above are just helpers, code starts here:
type Pipe<Nat> = () => void
type PipeOrder<X extends any[]> = { [K in keyof X]: X[K] extends Pipe<infer N> ? N : never }
type ValidOrder<P extends Pipe<Nat>[]> = IsOrdered<PipeOrder<P>> extends true ? P : never
type Storm = (...pipes: ValidOrder<Pipe<keyof Nats>[]>) => any
type Filter = () => Pipe<0>
type OrderBy = () => Pipe<4>
const filter: Filter = () => () => void 0
const orderBy: OrderBy = () => () => void 0
const storm: Storm =
(...pipes) => {
pipes.forEach(p => p())
}
// testing
type F = ReturnType<Filter>
type O = ReturnType<OrderBy>
const wrong: ValidOrder<[O, F, O]> = null as any
const right: ValidOrder<[F, O]> = null as any
const f = filter()
const o = orderBy()
const wrong2: ValidOrder<[typeof o, typeof f]> = null as any
const right2: ValidOrder<[typeof f, typeof o]> = null as any
const w = storm(o, f)
const r = storm(f, o)

在这个操场上,你可以看到w是一个有效的分配,即使它不应该是:

有人能发现为什么我的推论不起作用吗?直到最后2行的所有步骤都正确解析。

根本问题是Storm(storm()函数的类型)不是泛型的。因此,它的其余参数类型是ValidOrder<Pipe<keyof Nats>[]>,这与传递到函数中的实际参数无关:

type ImperfectStorm = (...pipes: ValidOrder<Pipe<keyof Nats>[]>) => any
type Param = Parameters<ImperfectStorm> // Pipe<keyof Nats>[]

显然,ValidOrder<Pipe<keyof Nats>[]>只解析为Pipe<keyof Nats>[],因此storm()将接受Pipe<keyof Nats>类型的任何参数而不关心顺序。

非泛型Storm的唯一工作方式是,如果rest参数类型是所有可能接受的参数类型集的并集,但事实并非如此。据推测,这样的结合将是无限的或几乎是无限的,所以你无论如何都不想尝试这样做。


修复方法是在传递到函数的实际参数类型中使Storm通用,因此编译器可以从中推断类型,并使用ValidOrder:进行检查

type Storm = <P extends Pipe<keyof Nats>[]>(...pipes: ValidOrder<P>) => any

有了这个类型声明,现在storm()将按照需要进行操作:

const w = storm(o, f); // error!
// -----------> ~
// Argument of type 'Pipe<4>' is not assignable to parameter of type 'never'
// const storm: <[Pipe<4>, Pipe<0>]>(...pipes: never) => any
const r = storm(f, o); // okay
// const storm: <[Pipe<0>, Pipe<4>]>(pipes_0: Pipe<0>, pipes_1: Pipe<4>) => any

编译器根据传入的参数推断出P,然后将它们与ValidOrder<P>进行检查。对于第一次调用,ValidOrder<P>never,它失败,而对于第二次调用,ValidOrder<P>P相同,它成功。

游乐场链接到代码

最新更新