如何使幻像类型与 TypeScript 中的方法一起使用?

How to make phantom types work with methods in TypeScript?

考虑以下使用虚拟类型的程序:

const strlen = (str: string) => str.length;

type Const<A, B> = { type: 'Const', value: A };

const Const = <A, B = never>(value: A): Const<A, B> => ({ type: 'Const', value });

const map = <A, B, C>(_f: (value: B) => C, { value }: Const<A, B>): Const<A, C> => Const(value);

const contramap = <A, B, C>(_f: (value: C) => B, { value }: Const<A, B>): Const<A, C> => Const(value);

const constant = Const(true);

map(strlen, constant); // works

contramap(strlen, constant); // works

Playground

上面的程序类型检查是因为推断出了正确的类型。 constant 值具有推断类型 Const<boolean, never>map 函数以 A = booleanB = stringC = number 类型调用。 contramap 函数以 A = booleanB = numberC = string.

类型调用

但是,最好使用方法而不是函数来编写上述表达式。因此,我尝试了以下方法:

const strlen = (str: string) => str.length;

interface Const<A, B> {
    map: <C>(f: (value: B) => C) => Const<A, C>;
    contramap: <C>(f: (value: C) => B) => Const<A, C>;
}

const Const = <A, B = never>(value: A): Const<A, B> => ({
    map: () => Const(value),
    contramap: () => Const(value)
});

const constant = Const(true);

constant.map(strlen); // works

constant.contramap(strlen); // error

Playground

如您所见,map 方法有效,但 contramap 方法无效。这是因为 constant 的类型是 Const<boolean, never> 并且它没有被方法调用细化,即对于 map 类型没有细化为 Const<boolean, string> 而对于 contramap 类型未细化为 Const<boolean, number>.

因此,mapcontramap 中的一个有效,但不能同时有效。如果对象的类型是 Const<boolean, never>contramap 不起作用。如果对象的类型是 Const<boolean, unknown> 那么 map 不起作用。

如何使用方法而不是函数使 mapcontramap 都工作?

我通过将 Const 接口的类型参数 B 设为幻像类型来解决这个问题。

const strlen = (str: string) => str.length;

interface Const<A, B> {
    map: <B, C>(f: (value: B) => C) => Const<A, C>;
    contramap: <B, C>(f: (value: C) => B) => Const<A, C>;
}

const Const = <A, B = never>(value: A): Const<A, B> => ({
    map: () => Const(value),
    contramap: () => Const(value)
});

const constant = Const(true);

constant.map(strlen); // works

constant.contramap(strlen); // works

Playground

Const 接口的类型参数 B 现在被 mapcontramap 的类型参数隐藏。这是有道理的,因为 Const 接口的类型参数 B 是幻像类型。因此,不应使用它。另一方面,mapcontramap 的调用者应该能够决定类型参数 B 应该被实例化的类型。