使用 class 继承在 TypeScript 中实现 Maybe/Option 类型
Implementing Maybe/Option type in TypeScript using class inheritance
我见过的大多数 Maybe/Option 类型的 Typescript 实现都使用带有标签的接口来区分 Some/None 变体。我见过一些没有的,实际上有单独的 classes for Some 和 None 都实现了一个通用的 Option 接口。作为实验,我想尝试一个使用继承的实现。所以这是我想出的简化版本:
abstract class Maybe<T> {
constructor(private value: T) {}
isSome(): this is Some<T> {
return this instanceof Some;
}
isNone(): this is None {
return !this.isSome();
}
and<U>(other: Maybe<U>): Maybe<U> {
return this.isNone() ? new None() : other;
}
unwrap(): T {
if (this.isNone()) throw new Error('called unwrap() on None');
return this.value;
}
unwrapOr(defaultValue: T): T {
return this.isNone() ? defaultValue : this.value;
}
}
class Some<T> extends Maybe<T> {
constructor(value: T) { super(value); }
}
class None extends Maybe<any> {
constructor() { super(null); }
}
但是我 运行 在 None
class 上将类型传递给 extends Maybe<...>
时遇到了一些问题。我尝试过使用 never
、null
和 any
,它们都会导致(不同的)编译器错误,迫使我在 Maybe's
方法中进行一些奇怪的转换。
你可以找到上面代码的游乐场here。
这是我的想法。 Maybe
上的泛型 Type T
表示包含值的类型,可以是任何类型。但是当 Maybe
是 None
时,它实际上应该是 never
,因为从概念上讲,它没有包含值。为此,我的第一个想法是让 None
扩展 Maybe<never>
但随后对 super(null)
的调用触发编译器错误,因为 null
不可分配给类型 [=15] =].
我想出的最有用的版本是 None
扩展 Maybe<any>
的版本,但这需要在 Maybe
内部进行一系列 (<Some<T>this).value
的转换方法(上面的代码中没有显示),我不喜欢那样。理想情况下,我想找到一个不需要使用 any
和强制转换的实现。我们将不胜感激。
使其正常工作所需的最小代码更改可能是这样的:
class None extends Maybe<never> {
constructor() { super(null!); }
}
也就是说,我们说 None
是一个 Maybe<never>
,其中 the never
type is the bottom type with no values. This is reasonable from a type system standpoint: the "right" type would be something like forall T. Maybe<T>
, but since TypeScript doesn't have "generic values" like microsoft/TypeScript#17574 there's no way to express such a type directly. Since it looks like Maybe<T>
should be covariant 在 T
中,那么您可以将 forall T. Maybe<T>
改写为 Maybe<forall T. T>
,相当于 Maybe<never>
,因为 TypeScript 中所有类型的交集是 never
.
但是为了使用您的代码构造一个Maybe<never>
,您需要向超级构造函数提供一个never
类型的值。这当然不可能发生。您超过了 null
,这不是 never
。我们可以使用 non-null assertion operator (postfix !
) 将 null
向下转换为 never
(null!
的类型是 NonNullable<null>
即 never
)。这在技术上是一个谎言,但它是一个相当无害的谎言,特别是如果您以后从未尝试观察该值。
如果您想在任何地方都保持类型安全,那么我认为您需要重构您的基础 class,这样它就不会假装做它不能做的事情。 Maybe<T>
可能没有 value
,因此 value
可能不应该在基础 class 中被要求。可以是 optional:
abstract class Maybe<T> {
constructor(public value?: T) { } // <-- optional
unwrap(): T {
if (this.isSome()) return this.value; // reverse check
throw new Error('called unwrap() on None');
}
unwrapOr(defaultValue: T): T {
return this.isSome() ? this.value : defaultValue; // reverse check
}
}
class Some<T> extends Maybe<T> {
declare value: T // <-- narrow from optional to required
constructor(value: T) { super(value); }
}
class None extends Maybe<never> {
constructor() { super(); } // <-- don't need an argument now
}
然后你可以 declare
the property in the subclass to be required (and I have to make it public
since private
doesn't let subclasses look at things and even protected
gets iffy with those type guard functions you have). Note that I switched your checks from isNone()
to isSome()
... the Maybe<T>
class is not known to be a union of Some<T> | None
,所以你不能使用 isNone()
的错误结果来断定 this.value
存在。相反,您应该使用 isSome()
.
的真实结果
最后,您可以将所有 Some
/None
特定功能从 Maybe
中移出,然后不再担心试图强制基本 class表现得像两个 subclasses。基于继承的多态性倾向于使用 subclasses 来覆盖方法,而不是使用检查 instanceof
的 superclass 方法。这类似于让 Maybe
只是一个 interface
,除了不需要检查当前 class:
的任何真正的多态方法
abstract class Maybe<T> {
abstract isSome(): this is Some<T>;
abstract isNone(): this is None;
abstract and<U>(other: Maybe<U>): Maybe<U>;
abstract unwrapOr(defaultValue: T): T;
}
class Some<T> extends Maybe<T> {
private value: T
constructor(value: T) { super(); this.value = value; }
isSome(): this is Some<T> { return true; }
isNone(): this is None { return false; }
and<U>(other: Maybe<U>) {
return other;
}
unwrap() { return this.value }
unwrapOr(defaultValue: T) { return this.value }
}
class None extends Maybe<never> {
isSome<T>(): this is Some<T> { return false }
isNone(): this is None { return true }
and<U>(other: Maybe<U>) {
return this;
}
unwrapOr<T>(defaultValue: T) { return defaultValue }
}
在这里,Maybe
除了抽象方法外什么都没有。如果你能想出一些不需要检查 this.isSome()
或 this.isNone()
的东西,那么它可以放在基础 class 中。这里唯一值得注意的是 Maybe<never>
的一些子 classing 涉及将非泛型方法(isSome
和 unwrapOr
)转换为泛型方法。
我见过的大多数 Maybe/Option 类型的 Typescript 实现都使用带有标签的接口来区分 Some/None 变体。我见过一些没有的,实际上有单独的 classes for Some 和 None 都实现了一个通用的 Option 接口。作为实验,我想尝试一个使用继承的实现。所以这是我想出的简化版本:
abstract class Maybe<T> {
constructor(private value: T) {}
isSome(): this is Some<T> {
return this instanceof Some;
}
isNone(): this is None {
return !this.isSome();
}
and<U>(other: Maybe<U>): Maybe<U> {
return this.isNone() ? new None() : other;
}
unwrap(): T {
if (this.isNone()) throw new Error('called unwrap() on None');
return this.value;
}
unwrapOr(defaultValue: T): T {
return this.isNone() ? defaultValue : this.value;
}
}
class Some<T> extends Maybe<T> {
constructor(value: T) { super(value); }
}
class None extends Maybe<any> {
constructor() { super(null); }
}
但是我 运行 在 None
class 上将类型传递给 extends Maybe<...>
时遇到了一些问题。我尝试过使用 never
、null
和 any
,它们都会导致(不同的)编译器错误,迫使我在 Maybe's
方法中进行一些奇怪的转换。
你可以找到上面代码的游乐场here。
这是我的想法。 Maybe
上的泛型 Type T
表示包含值的类型,可以是任何类型。但是当 Maybe
是 None
时,它实际上应该是 never
,因为从概念上讲,它没有包含值。为此,我的第一个想法是让 None
扩展 Maybe<never>
但随后对 super(null)
的调用触发编译器错误,因为 null
不可分配给类型 [=15] =].
我想出的最有用的版本是 None
扩展 Maybe<any>
的版本,但这需要在 Maybe
内部进行一系列 (<Some<T>this).value
的转换方法(上面的代码中没有显示),我不喜欢那样。理想情况下,我想找到一个不需要使用 any
和强制转换的实现。我们将不胜感激。
使其正常工作所需的最小代码更改可能是这样的:
class None extends Maybe<never> {
constructor() { super(null!); }
}
也就是说,我们说 None
是一个 Maybe<never>
,其中 the never
type is the bottom type with no values. This is reasonable from a type system standpoint: the "right" type would be something like forall T. Maybe<T>
, but since TypeScript doesn't have "generic values" like microsoft/TypeScript#17574 there's no way to express such a type directly. Since it looks like Maybe<T>
should be covariant 在 T
中,那么您可以将 forall T. Maybe<T>
改写为 Maybe<forall T. T>
,相当于 Maybe<never>
,因为 TypeScript 中所有类型的交集是 never
.
但是为了使用您的代码构造一个Maybe<never>
,您需要向超级构造函数提供一个never
类型的值。这当然不可能发生。您超过了 null
,这不是 never
。我们可以使用 non-null assertion operator (postfix !
) 将 null
向下转换为 never
(null!
的类型是 NonNullable<null>
即 never
)。这在技术上是一个谎言,但它是一个相当无害的谎言,特别是如果您以后从未尝试观察该值。
如果您想在任何地方都保持类型安全,那么我认为您需要重构您的基础 class,这样它就不会假装做它不能做的事情。 Maybe<T>
可能没有 value
,因此 value
可能不应该在基础 class 中被要求。可以是 optional:
abstract class Maybe<T> {
constructor(public value?: T) { } // <-- optional
unwrap(): T {
if (this.isSome()) return this.value; // reverse check
throw new Error('called unwrap() on None');
}
unwrapOr(defaultValue: T): T {
return this.isSome() ? this.value : defaultValue; // reverse check
}
}
class Some<T> extends Maybe<T> {
declare value: T // <-- narrow from optional to required
constructor(value: T) { super(value); }
}
class None extends Maybe<never> {
constructor() { super(); } // <-- don't need an argument now
}
然后你可以 declare
the property in the subclass to be required (and I have to make it public
since private
doesn't let subclasses look at things and even protected
gets iffy with those type guard functions you have). Note that I switched your checks from isNone()
to isSome()
... the Maybe<T>
class is not known to be a union of Some<T> | None
,所以你不能使用 isNone()
的错误结果来断定 this.value
存在。相反,您应该使用 isSome()
.
最后,您可以将所有 Some
/None
特定功能从 Maybe
中移出,然后不再担心试图强制基本 class表现得像两个 subclasses。基于继承的多态性倾向于使用 subclasses 来覆盖方法,而不是使用检查 instanceof
的 superclass 方法。这类似于让 Maybe
只是一个 interface
,除了不需要检查当前 class:
abstract class Maybe<T> {
abstract isSome(): this is Some<T>;
abstract isNone(): this is None;
abstract and<U>(other: Maybe<U>): Maybe<U>;
abstract unwrapOr(defaultValue: T): T;
}
class Some<T> extends Maybe<T> {
private value: T
constructor(value: T) { super(); this.value = value; }
isSome(): this is Some<T> { return true; }
isNone(): this is None { return false; }
and<U>(other: Maybe<U>) {
return other;
}
unwrap() { return this.value }
unwrapOr(defaultValue: T) { return this.value }
}
class None extends Maybe<never> {
isSome<T>(): this is Some<T> { return false }
isNone(): this is None { return true }
and<U>(other: Maybe<U>) {
return this;
}
unwrapOr<T>(defaultValue: T) { return defaultValue }
}
在这里,Maybe
除了抽象方法外什么都没有。如果你能想出一些不需要检查 this.isSome()
或 this.isNone()
的东西,那么它可以放在基础 class 中。这里唯一值得注意的是 Maybe<never>
的一些子 classing 涉及将非泛型方法(isSome
和 unwrapOr
)转换为泛型方法。