gpt4 book ai didi

TypeScript:解决 Map `getOrCreate` 辅助函数中的不健全问题

转载 作者:行者123 更新时间:2023-12-03 18:47:24 24 4
gpt4 key购买 nike

我有一个从 map 中获取条目的辅助函数,如果它不存在就添加它。

export function mapGetOrCreate<K, V>(map: Map<K, V>, key: K, valueFn: (key: K) => V): V {
let value = map.get(key);
if (value === undefined) {
value = valueFn(key);
assert(value !== undefined);
map.set(key, value);
}
return value;
}
( Full code in TS playground )
TypeScript 在泛型类型差异方面的不健全最终给我造成了这个实际问题:
type A = {a: number};
type B = A & {b: string};
type C = B & {c: boolean};

declare function createA(): A;
declare function createB(): B;
declare function createC(): C;

function f(m: Map<string, B>, element: Base) {
m.set('1', createA()); // TS error (good)
m.set('1', createB());
m.set('1', createC());

mapGetOrCreate(m, '1', createA); // missing error!
mapGetOrCreate(m, '1', createB);
mapGetOrCreate(m, '1', createC);
}
我发现将另一个类型参数( V2 )添加到函数签名“修复”了这个问题:
export function mapGetOrCreate2<K, V, V2 extends V>(map: Map<K, V>, key: K, valueFn: (key: K) => V2): V {
let value = map.get(key);
if (value === undefined) {
value = valueFn(key);
assert(value !== undefined);
map.set(key, value);
}
return value;
}
还是不健全,但至少TypeScript不能自动推断类型,这是一个小小的改进。
问题:
  • 有没有更好的方法来完成我正在做的事情?
  • mapGetOrCreate2 是否比原始代码更糟糕?
  • 最佳答案

    您正在寻找 microsoft/TypeScript#14829 中要求的非推论类型参数用法。对此没有官方支持,但是有许多技术可以实现这种效果,其中之一就是您已经在使用的技术。

    为了让以后遇到这个问题的任何人都清楚,这里的不健全之处在于,只要 Map<K, U> 可分配给 Map<K, T> ,TypeScript 就允许将 U 分配给 T :

    function technicallyUnsound<K, T, U extends T>(mapU: Map<K, U>) {
    const mapT: Map<K, T> = mapU;
    }
    只要您只是从 map 中读取,这实际上是完全合理的,但是当您写入它们时,您最终可能会遇到麻烦:
    function technicallyUnsound<K, T, U extends T>(mapU: Map<K, U>, k: K, t: T) {
    mapU.set(k, u); // error, as desired
    const mapT: Map<K, T> = mapU;
    mapT.set(k, t); // no error, uh oh
    }

    const m = new Map<string, Date>();
    technicallyUnsound(m, "k", {});
    m.get("k")?.getTime(); // compiles okay, but
    // RUNTIME ERROR: u is not defined
    这就是 TypeScript 的方式;它具有一组有用的功能,例如对象可变性、子类型化、别名和 method bivariance ,这些功能可以提高开发人员的生产力,但可以以不安全的方式使用。无论如何,有关此类健全性问题的更多详细信息,请参阅 this SO answer
    真的没有办法完全阻止这种情况。不管你做什么,即使你可以强化 mapGetOrCreate() ,你总是可以使用这样的别名来解决它:
    mapGetOrCreate2(m, "", createA) // error, but
    const n: Map<string, A> = m;
    mapGetOrCreate2(n, "", createA) // no error
    但是,考虑到这一点,强化 mapGetOrCreate() 的选项有哪些?

    您使用 mapGetOrCreate() 遇到的真正问题是 TypeScript 的泛型类型参数推断算法。让我们将 mapGetOrCreate() 归结为一个函数 g() ,我们忘记了键类型 K (仅使用 string )。在以下调用中:
    declare function g<T>(map: Map<string, T>, valueFn: (key: string) => T): T;
    g(m, createA); // no error, not good
    // function g<A>(map: Map<string, A>, valueFn: (key: string) => A): A
    编译器推断类型参数 T 应该由类型 A 指定,因为 valueFn 返回一个 A ,并且 Map<string, B> 也被认为是有效的 Map<string, A>
    理想情况下,您希望编译器仅从 T 推断 map,然后检查 valueFn 是否可分配给 (key: string) => TT 。在 valueFn 中,您只想使用 T 而不是推断它。
    换句话说,您正在寻找非推论类型参数的使用,如 microsoft/TypeScript#14829 所要求的那样。正如我在开头所说的,没有官方的方法可以做到这一点。

    让我们看看非官方的方式:
    一种非官方的方法是 use an additional type parameter U ,它是 constrained 到原始类型参数 T 。由于 U 将与 T 分开推断,因此从 T 的角度来看,它看起来是“非推断性的”。这是您对 mapGetOrCreate2 所做的:
    declare function g<T, U extends T>(map: Map<string, T>, valueFn: (key: string) => U): T;
    g(m, createA); // error! A is not assignable to B
    // function g<B, B>(map: Map<string, B>, valueFn: (key: string) => B): B
    g(m, createB); // okay
    g(m, createC); // okay
    另一种非官方方式是 intersect the "non-inferential" locations with {} ,它“降低了该推理站点的优先级”。这不太可靠,仅适用于 X 类型,其中 X & {} 不缩小 X(因此 X 中不能包含 undefinednull),但它也适用于这种情况:
    type NoInfer<T> = T & {};
    declare function g<T>(map: Map<string, T>, valueFn: (key: string) => NoInfer<T>): T;
    g(m, createA); // error! A is not assignable to type NoInfer<B>
    // function g<B>(map: Map<string, B>, valueFn: (key: string) => NoInfer<B>): B
    g(m, createB); // okay
    g(m, createC); // okay
    我知道的最后一种方法是使用 conditional types 的评估是 deferred for as-yet unresolved type parameters 的事实。所以 NoInfer<T> 最终会计算为 T ,但这会在类型推断发生后发生。它也可以在这里工作:
    type NoInfer<T> = [T][T extends any ? 0 : never];
    declare function g<T>(map: Map<string, T>, valueFn: (key: string) => NoInfer<T>): T;
    g(m, createA); // error! A is not assignable to type B
    // function g<B>(map: Map<string, B>, valueFn: (key: string) => B): B
    g(m, createB); // okay
    g(m, createC); // okay
    所有这三种方法都是变通方法,并不适用于所有情况。如果你对它的细节和讨论感兴趣,你可以通读 ms/TS#14829。我的主要观点是,如果您的技术适用于您的用例,那么它可能没问题,而且我不知道有任何明显优越的技术。
    我认为修改后的版本比原始版本更糟糕的唯一方法是它更复杂,需要更多的测试。您试图避免的问题实际上在实践中似乎并不经常出现(这就是方法二元性是语言的一部分的原因);既然您实际上遇到了问题,那么您可能应该真正解决它,因此增加的复杂性是值得的。但是,由于面对不健全的类型系统,这种强化从根本上是不可能的,因此很快就会出现 yield 递减点,之后最好只接受不健全并编写一些更具防御性的运行时检查,并放弃尝试开拓纯粹健全的领土。
    Playground link to code

    关于TypeScript:解决 Map `getOrCreate` 辅助函数中的不健全问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67746222/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com