[英]Implementing Maybe/Option type in TypeScript using class inheritance
我見過的大多數 Maybe/Option 類型的 Typescript 實現要么使用帶有標簽的接口來區分 Some/None 變體。 我見過一些沒有並且實際上有 Some 和 None 的單獨類,它們都實現了一個通用的 Option 接口。 作為實驗,我想嘗試使用 inheritance 的實現。 所以這是我想出的簡化版本:
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
方法中進行一些奇怪的轉換。
您可以在此處找到上述代碼的游樂場。
這是我的想法。 Maybe
上的泛型 Type T
表示包含值的類型,可以是任何類型。 但是當Maybe
是None
時,它真的應該是never
,因為從概念上講,它沒有包含的值。 與此保持一致,我的第一個想法是讓None
擴展Maybe<never>
但隨后對super(null)
的調用會觸發編譯器錯誤,因為null
不可分配給類型never
。
我想出的最有用的版本是None
擴展Maybe<any>
的版本,但這需要在Maybe
的方法中進行一堆(<Some<T>this).value
的轉換(上面的代碼中沒有顯示)和我不喜歡那樣。 理想情況下,我想找到一個不需要使用any
並在所有地方強制轉換的實現。 任何有助於實現這一目標的幫助將不勝感激。
使其工作所需的代碼的最小更改可能是這樣的:
class None extends Maybe<never> {
constructor() { super(null!); }
}
也就是說,我們說None
是Maybe<never>
,其中never
類型是沒有值的底部類型。 從類型系統的角度來看,這是合理的:“正確”類型類似於forall T. Maybe<T>
,但由於 TypeScript 沒有像microsoft/TypeScript#17574這樣的“通用值”,因此無法表達這樣的直接輸入。 由於看起來Maybe<T>
在T
中應該是協變的,那么您可以將forall T. Maybe<T>
改寫為Maybe<forall T. T>
,這相當於Maybe<never>
作為所有類型的交集TypeScript never
。
但是為了用你的代碼構造一個Maybe<never>
,你需要為超級構造函數提供一個never
類型的值。 這當然不可能發生。 您正在傳遞null
,這不是never
。 我們可以使用非空斷言運算符(后綴!
)將null
向下轉換為never
( null!
的類型是NonNullable<null>
,它是never
)。 這在技術上是一個謊言,但它是一個相當無害的謊言,特別是如果你以后從不嘗試觀察該值。
如果你想在任何地方都輸入安全,那么我認為你需要重構你的基礎 class ,這樣它就不會假裝做它不能做的事情。 Maybe<T>
可能沒有value
,因此在基本 class 中可能不需要value
。 它可能是可選的:
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
該屬性是必需的(我必須將其public
,因為private
不允許子類查看事物,甚至protected
與您擁有的那些類型保護函數不一致)。 請注意,我將您的檢查從isNone()
切換到isSome()
... Maybe<T>
class 不知道是Some<T> | None
的聯合。 Some<T> | None
,因此您不能使用isNone()
的錯誤結果來斷定this.value
存在。 相反,您應該使用isSome()
的真實結果。
最后,您可以將所有Some
/ None
特定功能從Maybe
中移出,然后不再擔心試圖強制基礎 class 表現得像兩個子類。 基於繼承的多態性傾向於使用子類來覆蓋方法,而不是使用檢查instanceof
的超類方法。 這類似於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 中找到 go。 這里唯一值得注意的是, Maybe<never>
的一些子類化涉及將非泛型方法( isSome
和unwrapOr
)轉換為泛型方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.