繁体   English   中英

链函数和从先前的返回推断类型

[英]Chain functions and infer types from previous return

是否可以从前一个链中声明的相应键中键入以下每个函数的 arguments?

const helloWorld = example(
    ({}) => ({ greeting: 'Hello', name: 'Thomas' }),
    ({ greeting, name }) => ({ clean: `${greeting} ${name}` }),
    ({ clean }) => ({ cleanLength: clean.length }),
    ({ name }) => ({ nameLength: name.length }),
)

从这里有这个CheckFuncs泛型,它检查每个 function 的类型是否相互对齐。

type Tail<T extends readonly any[]> =
    ((...a: T) => void) extends ((h: any, ...r: infer R) => void) ? R : never;

type CheckFuncs<T extends readonly ((x: any) => any)[]> = { [K in keyof T]:
    K extends keyof Tail<T> ? (
        [T[K], Tail<T>[K]] extends [(x: infer A) => infer R, (x: infer S) => any] ? (
            [R] extends [S] ? T[K] : (x: A) => S
        ) : never
    ) : T[K]
}

function journey<T extends readonly ((x: any) => any)[]>(...t: CheckFuncs<T>) {

}

这与那有点不同。

在这里,我们可以假设几件事:

  • 每个 function 必须只接受一个 {} 参数
  • 每个 function 必须返回一个 {}
  • 参数必须在链中声明,然后才能使用或传递到创建的 function。

理想的语法:

const helloWorld = example(
    ({ greeting, name }) => ({ clean: `${greeting} ${name}` }),
    ({ clean }) => ({ cleanLength: clean.length }),
    ({ name }) => ({ nameLength: name.length }),
)

helloWorld({ greeting: 'Hello', name: 'Thomas' })

这可能吗?


也对关键版本感兴趣:

const helloWorld = mac({
    clean: ({ greeting, name }) => `${greeting} ${name}`,
    cleanLength: ({ clean }) => clean.length,
    nameLength: ({ name }) => name.length,
})

这是一个尝试,但是:

'a' 在其自己的类型注释中被直接或间接引用。

const JourneyKeyed = <T extends JourneyKeyed.Objects<T>>(a: T) => {
    return a
}

const helloWorld = JourneyKeyed({
    greeting: ({ name }) => name === 'bob' ? 'Get out!' : 'Welcome',
    fullGreeting: ({ greeting, name }) => `${greeting} ${name}`,
})


namespace JourneyKeyed {
    type ThenArg<T> = T extends Promise<infer U> ? U : T
    type FirstArg<T extends any> =
        T extends [infer R, ...any[]] ? R :
        T extends [] ? undefined :
        T;
    type KeyedReturns<C, M extends Array<keyof C>> = {
        [K in M[number]]: C[K] extends ((...args: any[]) => any) ? FirstArg<ThenArg<ReturnType<C[K]>>> : never
    }
    type AllKeyedReturns<T> = KeyedReturns<typeof helloWorld, Array<keyof typeof helloWorld>>
    export type Objects<T extends object> = { [K in keyof T]: (a: AllKeyedReturns<T[K]>) => T[K] extends Func ? ReturnType<T[K]> : never }
}

操场

这相当丑陋,我认为我没有精力解释它。 这已经够混乱了,我强烈建议放弃任何需要对元组类型进行“减少”操作的东西,转而使用构建器 我将首先展示我正在做的事情的草图。 这是代码:

// extend as needed I guess
type LT = [never, 0, 0 | 1, 0 | 1 | 2, 0 | 1 | 2 | 3, 0 | 1 | 2 | 3 | 4,
  0 | 1 | 2 | 3 | 4 | 5, 0 | 1 | 2 | 3 | 4 | 5 | 6, 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
];
type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type UI<U> = UnionToIntersection<U> extends infer O ? { [K in keyof O]: O[K] } : never;
type P0<T> = T extends (x: infer A) => any ? A : never;
type Ret<T> = T extends (x: any) => infer R ? R : never;
type Idx<T, K, D = never> = K extends keyof T ? T[K] : D;

type ScanFuncs<T extends readonly ((x: any) => any)[]> = {
  [K in keyof T]: (x: UI<ReturnType<Idx<T, Idx<LT, K>>> | P0<T[0]>>) => Ret<T[K]>
}

function coalesce<T extends readonly ((x: any) => any)[]>(
  ...args: T & ScanFuncs<T>
): (x: P0<T[0]>) => UI<ReturnType<T[number]> | P0<T[0]>>;
function coalesce(...args: readonly ((x: any) => any)[]) {
  return (x: any) => args.reduce((a, s) => Object.assign(a, s(a)), x)
}

从根本上说,您正在检查元组中的每个 function 与所有前面的函数,因此您需要类似:对于元组T中的给定索引键K ,给我T[EverythingLessThan<K>]并操作它。 没有简单的方法来表示EverythingLessThan<K> ,所以我创建了一个名为LT的元组,其中K的硬编码值最高为"8" 它可以根据需要进行扩展,或者用一些聪明但不受支持的递归类型替换,只要它们都不会接近我负责的生产代码。

The ScanFuncs type alias converts a tuple T of one-arg function types into a compatible type, by comparing each function T[K] to another function whose return type is unchanged, but whose parameter type is the intersection of the first function's parameter type and所有先前的函数都返回类型'。 我在那里使用UnionToIntersection ,如果您的功能涉及工会本身,这可能会做一些奇怪的事情。 它可以被防范,但更复杂,所以我不打扰。

我实现了您的example ,我将其称为coalesce是因为需要一个更具启发性的名称,作为 function ,它采用T类型的单参数回调元组,使用ScanFuncs<T>检查它,并返回一个单参数 function 其参数type 是T[0]的类型,其返回类型是T[0]T的所有返回类型的交集。 让我们演示一下它的工作原理:

const f = coalesce(
  ({ x, y }: { x: number, y: string }) => ({ z: y.length === x }),
  ({ z }: { z: boolean }) => ({ v: !z }),
  ({ y }: { y: string }) => ({ w: y.toUpperCase() })
)
const r = f({ x: 9, y: "newspaper" })
/* const r: {
    x: number;
    y: string;
    z: boolean;
    v: boolean;
    w: string;
} */
console.log(r);
// { x: 9, y: "newspaper", z: true, v: false, w: "NEWSPAPER" }

看起来不错。

请注意,您几乎必须注释您的回调,例如({x, y}: {x: number, y: string}) =>而不是({x, y}) => ,因为后者将导致隐式any类型。 由于我之前向您提到设计限制,您希望让编译器从先前 arguments 的返回值推断参数类型的任何希望都应该被扼杀。


这个推理问题和元组归约操作的混乱都强烈地向我暗示,在 TypeScript 中执行此操作的惯用方式将改为使用构建器模式。 它可能看起来像这样:

type CollapseIntersection<T> =
  Extract<T extends infer U ? { [K in keyof U]: U[K] } : never, T>

class Coalesce<I extends object, O extends object> {
  cb: (x: I) => (I & O)
  constructor(cb: (x: I) => O) {
    this.cb = x => Object.assign({}, x, cb(x));
  }
  build() { return this.cb as (x: I) => CollapseIntersection<I & O> }
  then<T>(cb: (x: I & O) => T) {
    return new Coalesce<I, O & T>(x => {
      const io = this.cb(x);
      return Object.assign(io, cb(io));
    });
  }
}

这可能不是最好的实现,但您可以看到打字不那么疯狂。 CollapseIntersection确实是其中唯一“奇怪”的东西,这只是为了让像{x: 1, y: 2} & {z: 3} & {w: 4}这样的笨拙类型更容易作为{x: 1, y: 2, z: 3, w: 4}

构建器通过将后续then()函数折叠到其当前回调中来工作,并仅跟踪当前 output 类型和整体输入类型。

你像这样使用它:

const f = new Coalesce(
  ({ x, y }: { x: number, y: string }) => ({ z: y.length === x })
).then(
  ({ z }) => ({ v: !z })
).then(
  ({ y }) => ({ w: y.toUpperCase() })
).build();

请注意,类型推断现在有效,您不必在then()调用中注释zy 您仍然必须在初始new Coalesce()参数中注释xy ,但这是有道理的,因为编译器无处可推断它。 它的行为相同:

const r = f({ x: 9, y: "newspaper" })
/* const r: {
    x: number;
    y: string;
    z: boolean;
    v: boolean;
    w: string;
} */
console.log(r);
// { x: 9, y: "newspaper", z: true, v: false, w: "NEWSPAPER" }

看起来不错!


好的,希望有帮助; 祝你好运!

链接到代码

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM