我有一个类型相互关联的元组。在我的情况下,它是一个提取函数,它提取一个值,该值又用作另一个函数的输入。
从概念上讲,我正在寻找的东西是这样的,但这不能编译:
const a: <T>[(v:any) => T, (t:T) => void] = [ ... ]
Run Code Online (Sandbox Code Playgroud)
用例是这个。我有一个类型为的传入RPC消息any,以及一个具有众所周知的参数类型的API。我想构建一个包含两个参数的“接线计划”,一个是提取器函数,另一个是对应的API函数。
export interface API = {
saveModel : (model:Model) => Promise<boolean>,
getModel : (modelID:string) => Promise<Model>,
}
const api: API = { ... }
// this is the tuple type where i'd like to define that
// there's a relation between the second and third member
// of the tuple.
type WirePlan = [[string, (msg:any) => T, (t:T) => Promise<any>]]
const wirePlan: WirePlan = [[
['saveModel', (msg:any) => <Model>msg.model , api.saveModel],
['getModel' , (msg:any) => <string>msg.modelID, api.getModel],
]
const handleMessage = (msg) => {
const handler = wirePlan.find((w) => w[0] === msg.name)
const extractedValue = handler[1](msg)
return handler[2](extractedValue)
}
Run Code Online (Sandbox Code Playgroud)
我可以通过其他方式解决该问题,这让我感到惊讶,关于元组的某些事情我可能还不了解。
从概念上讲,我正在寻找的是这样的东西,但这不能编译:
Run Code Online (Sandbox Code Playgroud)const a: <T>[(v:any) => T, (t:T) => void] = [ ... ]
实际上,这与您想要的相反。利用函数类型的直觉,a: <T>(t: T) => T意味着您拥有一个适用于所有类型的函数。这是一个全称量词:实现a不知道是什么T;的用户a可以设置T为他们想要的任何内容。为您的元组执行此操作将是灾难性的,因为内部函数需要输出T任何值T,因此它们唯一能做的就是错误输出/永远循环/以某种方式或其他方式处于底部(它们必须返回never) .
你想要存在量化。a: ?T. [(v:any) => T, (t:T) => void]意味着a有一些T与之相关的类型。的实现a知道它是什么并且可以用它做任何它喜欢的事情,但是a现在的用户对它一无所知。实际上,与通用量化相比,它颠倒了角色。TypeScript 不支持存在类型(甚至不支持像 Java 通配符这样的超级基本形式),但它可以被模拟:
type WirePlanEntry = <R>(user: <T>(name: string, reader: (msg: any) => T, action: (t: T) => Promise<any>)) => R
type WirePlan = WirePlanEntry[]
Run Code Online (Sandbox Code Playgroud)
是的,就是一口。可以分解为:
// Use universal quantification for the base type
type WirePlanEntry<T> = [string, (msg: any) => T, (t: T) => Promise<any>]
// A WirePlanEntryConsumer<R> takes WirePlanEntry<T> for any T, and outputs R
type WirePlanEntryConsumer<R> = <T>(plan: WirePlanEntry<T>) => R
// This consumer consumer consumes a consumer by giving it a `WirePlanEntry<T>`
// The type of an `EWirePlanEntry` doesn't give away what that `T` is, so now we have
// a `WirePlanEntry` of some unknown type `T` being passed to a consumer.
// This is the essence of existential quantification.
type EWirePlanEntry = <R>(consumer: WirePlanEntryConsumer<R>) => R
// this is an application of the fact that the statement
// "there exists a T for which the statement P(T) is true"
// implies that
// "not for every T is the statement P(T) false"
// Convert one way
function existentialize<T>(e: WirePlanEntry<T>): EWirePlanEntry {
return <R>(consumer: WirePlanEntryConsumer<R>) => consumer(e)
}
// Convert the other way
function lift<R>(consumer: WirePlanEntryConsumer<R>): (e: EWirePlanEntry) => R {
return (plan: EWirePlanEntry) => plan(consumer)
}
Run Code Online (Sandbox Code Playgroud)
消费EWirePlanEntry看起来像
plan(<T>(eT: WirePlanEntry<T>) => ...)
// without types
plan(eT => ...)
Run Code Online (Sandbox Code Playgroud)
但如果你只是有消费者喜欢
function consume<T>(plan: WirePlanEntry<T>): R // R is not a function of T
Run Code Online (Sandbox Code Playgroud)
你会像这样使用它们
plan(consume) // Backwards!
lift(consume)(plan) // Forwards!
Run Code Online (Sandbox Code Playgroud)
但是,现在您可以拥有生产者。最简单的这样的生产者已经写好了:existentialize.
这是您的其余代码:
type WirePlan = EWirePlanEntry[]
const wirePlan: WirePlan = [
existentialize(['saveModel', (msg:any) => <Model>msg.model , api.saveModel]),
existentialize(['getModel' , (msg:any) => <string>msg.modelID, api.getModel ]),
]
const handleMessage = (msg: any) => {
let entry = wirePlan.find(lift((w) => w[0] === msg.name))
if(entry) {
entry(handler => {
const extractedValue = handler[1](msg)
return handler[2](extractedValue)
})
}
}
Run Code Online (Sandbox Code Playgroud)