In order to provide an answer to your inquiry, it is essential to consider the specific implementation of Maybe in use. Below, I am sharing the implementation that I currently employ. As it stands, you must explicitly annotate a function as a type guard for it to function properly. Although there has been discussion regarding the necessity of supporting `not T` types for `!isNone()` to operate, this has yet to materialize. Nonetheless, the following code does demonstrate functionality:
const MaybeTag = Symbol("Maybe");
type Nothing<T> = { [MaybeTag]?: T; name: "Nothing" } ;
type Just<T> ={ [MaybeTag]?: T; name: "Just"; value: T } ;
type Maybe<T> = Just<T> | Nothing<T>
const Maybe = {
Just<T>(value: T): Just<T> {
return { name: "Just", value };
},
Nothing<T = never>(): Nothing<T> {
return { name: "Nothing" };
},
isNothing<T>(maybe: Maybe<T>): maybe is Nothing<T> {
return maybe.name === "Nothing";
},
isJust<T>(maybe: Maybe<T>): maybe is Just<T> {
return maybe.name === "Just";
},
fromNull<T>(value: T | null | undefined): Maybe<T> {
if (value == null) return Maybe.Nothing();
return Maybe.Just(value);
}
}
const x: number[] = [1, 2, 3, null]
.map(Maybe.fromNull)
.filter(Maybe.isJust)
.map(x => x.value) // x is Just<number> as expected
const x: number[] = [1, 2, 3, null]
.map(Maybe.fromNull)
// Note the explicit type guard
.filter(function<T>(x: Maybe<T>): x is Just<T> {
return !Maybe.isNothing(x);
})
.map(x => x.value) // x is Just<number> as expected