summaryrefslogtreecommitdiff
path: root/u/types/fn/optional.ts
blob: 504e496f8f36bf29067d57b2786338da23fb7559 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { type Mapper, Predicate, type Supplier, Tagged, isTagged } from '@emprespresso/pengueno';

export type MaybeGiven<T> = T | undefined | null;

export const IOptionalTag = 'IOptional' as const;
export type IOptionalTag = typeof IOptionalTag;
export const isOptional = <T>(o: unknown): o is IOptional<T> => isTagged(o, IOptionalTag);
export class IOptionalEmptyError extends Error {}
export interface IOptional<t, T extends NonNullable<t> = NonNullable<t>> extends Tagged<IOptionalTag>, Iterable<T> {
    readonly move: <_T>(t: MaybeGiven<_T>) => IOptional<_T>;
    readonly map: <_T>(mapper: Mapper<T, MaybeGiven<_T>>) => IOptional<_T>;
    readonly filter: (mapper: Predicate<T>) => IOptional<T>;
    readonly flatMap: <_T>(mapper: Mapper<T, MaybeGiven<IOptional<_T>>>) => IOptional<_T>;
    readonly orSome: (supplier: Supplier<MaybeGiven<t>>) => IOptional<T>;
    readonly get: Supplier<T>;
    readonly present: Supplier<boolean>;
}

type OSomeTag = typeof OSomeTag;
const OSomeTag = 'O.Some' as const;
interface Some<T> extends Tagged<OSomeTag> {
    value: NonNullable<T>;
}

const ONoneTag = 'O.None' as const;
type ONoneTag = typeof ONoneTag;
interface None extends Tagged<ONoneTag> {}

const isNone = (o: unknown): o is None => isTagged(o, ONoneTag);
const isSome = <T>(o: unknown): o is Some<T> => isTagged(o, OSomeTag);

class _Tagged implements Tagged<IOptionalTag> {
    protected constructor(public readonly _tag = IOptionalTag) {}
}

export class Optional<t, T extends NonNullable<t> = NonNullable<t>> extends _Tagged implements IOptional<T> {
    private constructor(private readonly self: Some<T> | None) {
        super();
    }

    public move<_T>(t: MaybeGiven<_T>): IOptional<_T> {
        return this.map(() => t);
    }

    public orSome(supplier: Supplier<MaybeGiven<t>>): IOptional<T> {
        if (isNone(this.self)) return Optional.from(supplier());
        return this;
    }

    public get(): T {
        if (isNone(this.self)) throw new IOptionalEmptyError('called get() on None optional');
        return this.self.value;
    }

    public filter(mapper: Predicate<T>): IOptional<T> {
        if (isNone(this.self) || !mapper(this.self.value)) return Optional.none();
        return Optional.some(this.self.value);
    }

    public map<_T>(mapper: Mapper<T, MaybeGiven<_T>>): IOptional<_T> {
        if (isNone(this.self)) return Optional.none();
        return Optional.from(mapper(this.self.value)) as IOptional<_T>;
    }

    public flatMap<_T>(mapper: Mapper<T, MaybeGiven<IOptional<_T>>>): IOptional<_T> {
        if (isNone(this.self)) return Optional.none();
        return Optional.from(mapper(this.self.value))
            .orSome(() => Optional.none())
            .get();
    }

    public present() {
        return isSome(this.self);
    }

    *[Symbol.iterator]() {
        if (isSome(this.self)) yield this.self.value;
    }

    static some<t, T extends NonNullable<t> = NonNullable<t>>(value: T): IOptional<T> {
        return new Optional({ value, _tag: OSomeTag });
    }

    private static readonly _none = new Optional({ _tag: ONoneTag });
    static none<T>(): IOptional<T> {
        return this._none as unknown as IOptional<T>;
    }

    static from<t, T extends NonNullable<t> = NonNullable<t>>(value: MaybeGiven<t>): IOptional<T> {
        if (value === null || value === undefined) return Optional.none<T>();
        return Optional.some(<T>value);
    }
}