typescript - 如何保护柯里化函数中参数的顺序?
问题描述
我不想过多介绍细节以保持简短,但是我有一种称为true 的类型,当数字确实在上升时IsOrdered<[1,2,4]>
解析为true而对于其他所有内容为falsetype f = IsOrdered<[3,2]> // false
。它可以正常工作。
现在我有以下代码不起作用。要点是storm(...pipes)
管道是类型元素的函数Pipe<number>
。Number 用于在类型级别上标记 Pipe 并使用该数字来验证它们的顺序。
我想要实现的是storm()将拒绝接受未正确订购的管道。正如您在代码中Filter
看到Pipe<0>
的OrderBy
那样Pipe<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)
在这个Playground中,您可以看到w是一个有效的分配,即使它不应该是:
有人能发现为什么我的推理不起作用吗?直到最后两行的所有步骤都正确解析。
解决方案
根本问题是,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>[]>
解析为 just Pipe<keyof Nats>[]
,因此storm()
将接受任何类型的参数Pipe<keyof Nats>
而不关心顺序。
非泛型Storm
起作用的唯一方法是,如果其余参数类型是所有可能的可接受参数类型集的联合,但事实并非如此。大概这样的联合将是无限的或几乎是无限的,因此您无论如何都不想尝试这样做。
修复方法是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>
isnever
并且它失败,而对于第二次调用,ValidOrder<P>
is 相同P
并且它成功。
推荐阅读
- kubernetes - “istioctl manifest apply”和“istioctl install”有什么区别?
- vue.js - 连接时禁用空间
- python - 将应用程序添加到 Anaconda Navigator
- php - PHP file_get_contents() 汉字错误码
- reactjs - useState 指的是陈旧的值
- c# - PlatformNotSupportedException:此平台不支持 Microsoft.Data.SqlClient
- python - 在 python 3 中替换 base64 编码中的填充
- angular - RXJS - BehaviorSubject:正确使用 .value
- postgresql - 将 postgreSQL 表从模式复制到雪花的最简单方法是什么?
- javascript - 为“未来”时间生成按钮?