使用 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<...> 时遇到了一些问题。我尝试过使用 nevernullany,它们都会导致(不同的)编译器错误,迫使我在 Maybe's 方法中进行一些奇怪的转换。

你可以找到上面代码的游乐场here

这是我的想法。 Maybe 上的泛型 Type T 表示包含值的类型,可以是任何类型。但是当 MaybeNone 时,它实际上应该是 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 covariantT 中,那么您可以将 forall T. Maybe<T> 改写为 Maybe<forall T. T>,相当于 Maybe<never>,因为 TypeScript 中所有类型的交集是 never.

但是为了使用您的代码构造一个Maybe<never>,您需要向超级构造函数提供一个never类型的值。这当然不可能发生。您超过了 null,这不是 never。我们可以使用 non-null assertion operator (postfix !)null 向下转换为 nevernull! 的类型是 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 涉及将非泛型方法(isSomeunwrapOr)转换为泛型方法。


Playground link to code