【问题标题】:How do I type parameterize a tuple?如何键入参数化元组?
【发布时间】:2018-02-21 10:13:45
【问题描述】:

我有一个类型相互关联的元组。在我的例子中,它是一个提取器函数,它提取一个值,然后用作另一个函数的输入。

从概念上讲,我正在寻找的是这样的东西,但这不会编译:

const a: <T>[(v:any) => T, (t:T) => void] = [ ... ]

用例是这样的。我有一个 any 类型的传入 RPC 消息,以及一个具有众所周知的参数类型的 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)
}

我可以通过其他方式解决这个问题,只是让我感到震惊,可能有一些关于我不理解的元组。

【问题讨论】:

  • 张贴虚构的语法不是很有帮助 - 你能说明你将如何使用这个 a 值,即你想合法的例子和你想非法的例子?
  • 好的。我用一个具体的例子来充实它。
  • 不确定我是否理解这个问题,但这可能有帮助吗? type getSet&lt;T&gt; = [()=&gt;T,(val:T)=&gt;T]
  • @ThomasDevries 这可能是一种方式。

标签: typescript


【解决方案1】:

从概念上讲,我正在寻找的是这样的东西,但这不会编译:

const a: <T>[(v:any) => T, (t:T) => void] = [ ... ]

实际上,这与您想要的相反。根据函数类型的直觉,a: &lt;T&gt;(t: T) =&gt; T 意味着你有一个适用于所有类型的函数。这是一个通用量词:a 的实现不知道T 是什么; a 的用户可以将T 设置为任何他们想要的。为您的元组执行此操作将是灾难性的,因为无论T 是什么,内部函数都需要输出T 的值,因此他们唯一能做的就是错误输出/永远循环/以某种方式处于底部或另一个(他们必须返回never)。

你想要existential quantificationa: ∃T. [(v:any) =&gt; T, (t:T) =&gt; void] 表示 a 有一些类型 T 与之关联。 a 的实现知道它是什么并且可以随心所欲地使用它,但是a 的用户现在对此一无所知。实际上,与通用量化相比,它颠倒了角色。 TypeScript doesn't have support for existential types(甚至不是像 Java 通配符这样的超级基本形式),但它是 can be simulated

type WirePlanEntry = <R>(user: <T>(name: string, reader: (msg: any) => T, action: (t: T) => Promise<any>)) => R
type WirePlan = WirePlanEntry[]

是的,那是一口。可以分解为:

// 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)
}

消费EWirePlanEntry看起来像

plan(<T>(eT: WirePlanEntry<T>) => ...)
// without types
plan(eT => ...)

但如果你只有这样的消费者

function consume<T>(plan: WirePlanEntry<T>): R // R is not a function of T

你会像使用它们一样

plan(consume) // Backwards!
lift(consume)(plan) // Forwards!

不过,现在你可以拥有制作人了。最简单的这种生产者已经写好了: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)
    })
  }
}

In Action

【讨论】:

    猜你喜欢
    • 2015-05-11
    • 2019-01-13
    • 2018-11-04
    • 1970-01-01
    • 2022-01-06
    • 2021-12-15
    • 2022-08-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多