如何定义具有键类型和值类型之间相关性的 Map,而它们都是并集

2024-02-29

这是显示我想要实现的目标的示例。除了两个问题之外,它几乎可以工作:

  1. 设置不显示错误代码的错误
  2. Immer Draft 类型(或任何 DeepWritable 实用程序类型)完全搞乱了这个技巧

基本上在我看来,我在这里所做的事情并不是什么真正的事情。所以问题是:还有其他方法可以做同样的事情吗?

type Opaque<Type, Token = unknown> = Type & {
  readonly __opaque__: Token
}

type AnimalId = CatId | DogId

type Animal = Cat | Dog

type CatId = Opaque<number, Cat>

type Cat = {
  readonly kind: 'Cat'
  readonly id: CatId
}

type DogId = Opaque<number, Dog>

type Dog = {
  readonly kind: 'Dog'
  readonly id: DogId
}

const cat: Cat = {
    kind: 'Cat',
    id: 1 as CatId,
}

const dog: Dog = {
    kind: 'Dog',
    id: -1 as DogId,
}

const animals: Map<CatId, Cat> & Map<DogId, Dog> & Map<AnimalId, Animal> = new Map()

animals.set(cat.id, cat) // no error should be here

animals.set(cat.id, dog) // wanna see error here

const test1: Cat | undefined = animals.get(cat.id) // no error should be here

const test2: Dog | undefined = animals.get(dog.id) // no error should be here

const test4: Animal | undefined = animals.get(1 as AnimalId) // no error should be here

const test3 = animals.get(3) // wanna see error here

十字路口Map<CatId, Cat> & Map<DogId, Dog> should 概念上足以给你你想要的行为,但在实践中这是行不通的。函数类型的交集产生超载 https://www.typescriptlang.org/docs/handbook/functions.html#overloads,并且解决了 TypeScript 中的重载调用签名分别地。它们不会组合在一起以允许单个调用调用两个调用签名(请参阅微软/TypeScript#14107 https://github.com/microsoft/TypeScript/issues/14107)。所以只要Map<CatId, Cat> & Map<DogId, Dog>,你不能打电话animals.get(1 as AnimalId); an AnimalId既不是CatId nor a DogId,根据两个单独的调用签名中的每一个的要求。


为了解决这个问题,您显然添加了“缺失”Map<AnimalId, Animal>。不幸的是,这太过分了。你不仅得到想要的get()行为,你得到不受欢迎的 set()行为。自从cat.id is an AnimalId, and dog is an Animal, a Map<AnimalId, Animal>肯定会让你打电话animals.set(cat.id, dog)。我不会深入探讨协变和逆变的迂腐细节,但一般来说,如果阅读接受事物并集,那么writing应该接受交叉点-事物,而不是工会。所以唯一的方法是Map<AnimalId, Animal> you'd like支持的是涉及reading内容。

对我们来说幸运的是,有一个ReadonlyMapTypeScript 标准库中定义的接口 https://github.com/microsoft/TypeScript/blob/d7d8f33def2affef86ce1d0f9fb604523ef515ca/lib/lib.es2015.collection.d.ts#L38-L43这就是为了这个目的。所以我倾向于注释animals像这样:

const animals: Map<CatId, Cat> & Map<DogId, Dog>
  & ReadonlyMap<AnimalId, Animal> = new Map();

一旦你这样做了,你就会得到你正在寻找的行为,至少对于你的示例代码来说:

animals.set(cat.id, cat) // okay
animals.set(cat.id, dog) // error, no overload matches this call
const test1: Cat | undefined = animals.get(cat.id) // okay
const test2: Dog | undefined = animals.get(dog.id) // okay
const test4: Animal | undefined = animals.get(1 as AnimalId) // okay
const test3 = animals.get(3) // error, number is not AnimalId

当然,可能还有该定义不支持的其他用例。重载确实有一些奇怪的怪癖。如果您真的很关心,您可能需要手写自己的界面,其行为完全符合您期望的多类型Map行动。这是我之前针对不同案例做过的一个有趣的练习(请参阅这个问题 https://stackoverflow.com/questions/54907009/typescript-how-can-i-make-entries-in-an-es6-map-based-on-a-union-type)老实说,这并没有那么可怕。但我不会在这里讨论这一点,特别是如果上面的更简单的交集适用于您的用例。

Playground 代码链接 https://www.typescriptlang.org/play?#code/C4TwDgpgBA8mCGBHArhAPAFXBANFDA9gNYQB2UAvFMqUaQQO6kB8l+2UAZFAN4BQUKACcI8ACYFSAGxBQA+nIIIUEBQC58xMnwC+fPqEhQAgqQCWAW3hSAkmLYBheMDtQAPlAAiBAOZ39htCmltaOzu5evgEcTi72VHBIqGikyBYARhBCeLHM0UaxbPyCIuKSMlBEZqRiGgDksXUCwqIS0rJmtVCx-noGHN5+8bDKyakZWXiDef1Gg0XNpW0VVTX1g00lreUdXYO9+gDGkgDOwFCHzhqFVMWV1V0NznU4zZ0aAIxQ8CfdznavPrHUhnKASHwaea3ZqrR4bV6Cd5QAC0Xx+kSGgKOp3O8HMVikJw0AFl4GA0D0xDlnKxuKTyfsqRi8oJuAAlbbteloYIEgEmfHWVhUUgQBhQekACgAlABufR8PEhQkAOhOEGAksuwBVnTw2ulUAA9EaoMR4CBFYLVerNdrdUzwYaTVAskICNkoPQzQA3LJSAjiKBWYCHAAWEF+wDDZl+lykUj4wNBwEjwA+13CHhoYggADNqhBhkqCScVT4NVrnA7nabzZbk+dU2cAEyQ3wRHP5wvF61liua8E1411ogWpM4qDN4AAFg0vNC2Zq3dFveV-craN+C9sYlrZrHDcn04AzGwS9YN5qT-u3R68ONMkIoLGvQRzju7EA

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何定义具有键类型和值类型之间相关性的 Map,而它们都是并集 的相关文章

随机推荐

  • 带标签的维基数据 SPARQL 查询不起作用

    我不明白为什么通过这个查询我无法获得运动和流派标签 SELECT DISTINCT item itemLabel value inception creatorLabel image group concat genreLabel sepa
  • 如何动态导入 python 模块函数?

    假设my function 位于 my apps views 我想导入my function动态地不使用类似的东西exec or eval 有什么办法可以实现这一点吗 我想做类似的事情 my function import func my
  • 如何在msbuild文件中给出相对路径?

    我正在编写一个 msbuild 文件来使用 galio 运行测试 现在 我需要给出
  • 如何在汇编中通过字符串进行索引

    给定变量 var1 db abcdefg NULL 我将如何执行循环来导航每个字母 在 C 中 您可以在循环内执行类似 var x 的操作 然后每次递增 x 有任何想法吗 在 C 和 C 中 字符串以 NUL 结尾 这意味着将 ASCII
  • 我可以向量化这个Python代码吗?

    我编写了这段 python 代码来获取标签的邻居 一组共享一些公共属性的像素 标签的邻居被定义为位于边界另一侧的其他标签 相邻标签共享边界 所以 我写的代码可以工作 但速度非常慢 segments It is a 2 dimensional
  • 删除 sourceSets.main.runtimeClasspath 中的 jar

    我的 gradle 中有这个 sourceSets main compileClasspath configurations provided runtimeClasspath configurations provided test co
  • 从选择框中删除重复条目

    我如何使用 jQuery 删除重复项
  • 如何制作在 iOS 上的 VLC 中播放的音乐文件的 URL?

    我想通过我的网站向 iPhone 和 iPad 用户提供 MP3 和其他文件格式 VLC 的 iOS 应用程序似乎符合要求 但他们说 Additionally third party websites and apps may includ
  • 从 VBA 项目中删除密码

    如何以编程方式从 Excel VBA 项目中删除 已知 密码 需要明确的是 我想从 VBA 项目中删除密码 而不是从工作簿或任何工作表中删除密码 删除 VBA 项目密码的另一种方法是 使用十六进制编辑器打开 xls 文件 即十六进制编辑ht
  • 如何调试 Captive Portal 中的浏览器?

    强制门户有一个浏览器 当您尝试连接到无线网络时 该浏览器有时会打开 有谁知道我该如何调试它 我已经尝试过的 我尝试运行模拟器 但模拟器无法打开门户 我尝试过将 iPhone 连接到我的 Mac 并使用 Safari 开发进行调试 但这仅在
  • 如何在雪豹上安装gem pg

    我需要在雪豹上安装 gem pg 因为我正在 Rails 代码库上运行 rake 我没有使用 postgres 这是我收到的错误 sudo gem install pg Password Sorry try again Password B
  • 自动映射器有什么用?

    What s 自动映射器 http www codeplex com AutoMapper for 它将如何帮助我处理域和控制器层 asp net mvc 也许一个例子会有所帮助 假设您有一个很好标准化的数据库模式 如下所示 Orders
  • Python——词法分析和标记化

    我希望加快我的发现过程 因为这是我第一次涉足词法分析领域 也许这甚至是一条错误的道路 首先 我将描述我的问题 我有非常大的属性文件 大约 1 000 个属性 经过提炼后 实际上只有大约 15 个重要属性 其余属性可以生成或很少更改 因此 例
  • 在加载时应用选择背景颜色?

    请查看以下链接以查看我的代码的运行情况 http codepen io DigitalSquid pen mAkuC http codepen io DigitalSquid pen mAkuC 如何使背景颜色出现在页面加载时 on win
  • MVC 模式中的“Hello World”

    在面试某家公司的时候 我被问到了这个问题 你知道哪些设计模式 然后我被告知基于 MVC 设计模式编写最简单的 hello world 应用程序 我想出了一个 JavaScript 程序 var arr a b c d this is an
  • 应用 pyspark ALS 的“recommendProductsForUsers”时出现 StackOverflow 错误(尽管可用 >300GB RAM 的集群)

    寻找专业知识来指导我解决以下问题 背景 我正在尝试使用受启发的基本 PySpark 脚本这 例子 https github com GoogleCloudPlatform spark recommendation engine blob m
  • Laravel 至少需要一个字段

    我有两个字段billable option and billable option yes 我想验证至少一个字段是必填字段 我努力了 this gt validate request billable option gt required
  • 获取linux可执行文件加载地址(__builtin_return_address和addr2line)

    我正在编写一些代码来存储每个内存分配的回溯 然后 我将这些列表写入文件以进行离线分析 在win32中我使用 AddressOfReturnAddress然后手动创建回溯 由于我使用的每次运行的地址都是随机的GetModuleInformat
  • 无法在 Crashlytics 中获取本机崩溃的堆栈跟踪

    我有一个 Android 项目 其中包含本机库 so 我已将 firebase crashlytics 集成到我的项目中 我能够在 firebase crashlytics 中获取 Java 崩溃的崩溃转储 但是 如果发生本机崩溃 堆栈跟踪
  • 如何定义具有键类型和值类型之间相关性的 Map,而它们都是并集

    这是显示我想要实现的目标的示例 除了两个问题之外 它几乎可以工作 设置不显示错误代码的错误 Immer Draft 类型 或任何 DeepWritable 实用程序类型 完全搞乱了这个技巧 基本上在我看来 我在这里所做的事情并不是什么真正的