首页 > 解决方案 > 如何保护柯里化函数中参数的顺序?

问题描述

我不想过多介绍细节以保持简短,但是我有一种称为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是一个有效的分配,即使它不应该是:

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

标签: typescripttypestypescript-typingstype-inferencetypescript-generics

解决方案


根本问题是,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并且它成功。

Playground 代码链接


推荐阅读