Skip to content

Commit

Permalink
add methods to work with array
Browse files Browse the repository at this point in the history
  • Loading branch information
vojtatranta committed Nov 26, 2024
1 parent 97673f6 commit dcc3c7f
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 36 deletions.
120 changes: 120 additions & 0 deletions src/Maybe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,5 +173,125 @@ describe("Maybe", () => {
const result: Maybe<number> = inputAsMaybeFunc("test");
});
});

describe("extraction of array elements", () => {
describe("fromFirst()", () => {
it("fromFirst()", () => {
const result: Maybe<string> = Maybe.fromFirst(["a", "b", "c"]);

expect(result.getValue()).toEqual("a");
});

it("should mantain the constant type of the first element", () => {
const result = Maybe.fromFirst([

Check failure on line 186 in src/Maybe.test.ts

View workflow job for this annotation

GitHub Actions / test-job

Argument of type 'readonly ["a", "b", "c"]' is not assignable to parameter of type 'any[] | readonly any[][] | null | undefined'.
"a" as const,
"b" as const,
"c" as const,
] as const);

result.map((value: "a") => {
expect(value).toEqual("a");
});
});

it("should mantain the type of the first element", (callback) => {
const result = Maybe.fromFirst(["a", "b", "c"]);

result.map((value: string) => {
expect(value).toEqual("a");
callback();
});
});

it("should apply the default value when the first element is null", () => {
const result = Maybe.fromFirst([null, "b", "c"]);

const dConst: string = result.getValue("d");

expect(dConst).toEqual("d");
});
});

describe("fromLast()", () => {
it("should get the last element", () => {
const result: Maybe<string> = Maybe.fromLast(["a", "b", "c"]);

expect(result.getValue()).toEqual("c");
});

it("should map the last element", (callback) => {
const result = Maybe.fromLast([
"a" as const,
"b" as const,
"c" as const,
]);

result.map((value: "c") => {
expect(value).toEqual("c");
callback();
});
});

it("should apply the default value when the last element is null", () => {
const result = Maybe.fromLast(["a", "b", null]);

expect(result.getValue("d")).toEqual("d");
});

it("should apply the default value when the last element is undefined", () => {
const result = Maybe.fromLast(["b", "c", undefined]);

expect(result.getValue("X")).toEqual("X");
});

it("should mantain the constant type of the last element", (callback) => {
const result = Maybe.fromLast([

Check failure on line 248 in src/Maybe.test.ts

View workflow job for this annotation

GitHub Actions / test-job

Argument of type 'readonly ["a", "b", "c"]' is not assignable to parameter of type 'any[] | readonly any[][] | null | undefined'.
"a" as const,
"b" as const,
"c" as const,
] as const);

result.map((value: "c") => {
expect(value).toEqual("c");
callback();
});
});
});

describe("fromNth()", () => {
it("should get the nth element", () => {
const result: Maybe<string> = Maybe.fromNth(["a", "b", "c"], 1);

expect(result.getValue()).toEqual("b");
});

it("should map the nth element", (callback) => {
const result = Maybe.fromNth(
["a" as const, "b" as const, "c" as const],
1
);

result.map((value: string) => {
expect(value).toEqual("b");
callback();
});
});

it("should apply the default value when the nth element is null", () => {
const result = Maybe.fromNth(["a", "b", null], 2);

expect(result.getValue("X")).toEqual("X");
});

it("should mantain the type of the nth element in a mixed array", (callback) => {
const result = Maybe.fromNth(["a", "b", 123, null], 2);

result.map((value: string | number) => {
expect(value).toEqual(123);
callback();
});
});
});
});
});
});
112 changes: 76 additions & 36 deletions src/Maybe.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,124 @@
type NonNullableMaybe<T, D = never> = T extends Maybe<infer U> ? U extends null ? D : Maybe<U> : D;
type NonNullablePart<T> = T extends null ? never : T
type UnpackedMaybe<T> = T extends Maybe<infer U> ? U : T
type NonNullableMaybe<T, D = never> = T extends Maybe<infer U>
? U extends null
? D
: Maybe<U>
: D;
type NonNullablePart<T> = T extends null ? never : T;
type UnpackedMaybe<T> = T extends Maybe<infer U> ? U : T;
type Last<T extends any[]> = T extends [...infer Rest, infer L] ? L : never;

export class Maybe<T> {
private value: T | null | undefined
private value: T | null | undefined;

constructor(value: T | null | undefined) {
this.value = value
this.value = value;
}

static of<NV>(value: NV | null | undefined) {
return new Maybe<NV>(value)
return new Maybe<NV>(value);
}

static asMaybeInput<NV, R>(callback: (input: Maybe<NV>) => R): (externalInput: NV) => R {
return (externalInput: NV): R => callback(Maybe.of<NV>(externalInput))
static asMaybeInput<NV, R>(
callback: (input: Maybe<NV>) => R
): (externalInput: NV) => R {
return (externalInput: NV): R => callback(Maybe.of<NV>(externalInput));
}

static asInput<NV, R>(callback: (input: NonNullable<NV>) => R): (externalInput: NV) => Maybe<R> {
return (externalInput: NV): Maybe<R> => Maybe.of<NV>(externalInput).map(callback)
static asInput<NV, R>(
callback: (input: NonNullablePart<NV>) => R
): (externalInput: NV) => Maybe<R> {
return (externalInput: NV): Maybe<R> =>
Maybe.of<NV>(externalInput).map(callback);
}

static all<T extends unknown[]>(maybies: [...T]): Maybe<{ -readonly [P in keyof T]: UnpackedMaybe<T[P]> }> {
const results: unknown[] = []
static all<T extends unknown[]>(
maybies: [...T]
): Maybe<{ -readonly [P in keyof T]: UnpackedMaybe<T[P]> }> {
const results: unknown[] = [];

const allMaybies = maybies.map((value) => value instanceof Maybe ? value : Maybe.of(value))
allMaybies.forEach((maybeValue) => maybeValue.map((truthyValue) => {
results.push(truthyValue)
}))
const allMaybies = maybies.map((value) =>
value instanceof Maybe ? value : Maybe.of(value)
);
allMaybies.forEach((maybeValue) =>
maybeValue.map((truthyValue) => {
results.push(truthyValue);
})
);

if (results.length === maybies.length) {
return Maybe.of(results) as Maybe<{ -readonly [P in keyof T]: UnpackedMaybe<T[P]> }>
return Maybe.of(results) as Maybe<{
-readonly [P in keyof T]: UnpackedMaybe<T[P]>;
}>;
}

return Maybe.of(null as unknown as { -readonly [P in keyof T]: UnpackedMaybe<T[P]> })
return Maybe.of(
null as unknown as { -readonly [P in keyof T]: UnpackedMaybe<T[P]> }
);
}

map<R>(mapper: (value: NonNullable<T>) => R): Maybe<NonNullablePart<R>> {
static fromFirst<T extends any[]>(
someArray: T | readonly T[] | null | undefined
): Maybe<T[0]> {
return Maybe.fromNth<T, 0>(someArray, 0);
}

static fromLast<T extends any[]>(
someArray: T | readonly T[] | null | undefined
): Maybe<Last<T>> {
return Maybe.fromNth(someArray, (someArray?.length ?? 0) - 1);
}

static fromNth<T extends any[], N extends number>(
someArray: T | null | readonly T[] | undefined,
n: N
): Maybe<T[N]> {
return Maybe.of<T[N]>(someArray?.[n]);
}

map<R>(mapper: (value: NonNullablePart<T>) => R): Maybe<NonNullablePart<R>> {
if (this.value != null) {
return new Maybe<NonNullablePart<R>>(
mapper(this.value) as NonNullablePart<R>
)
mapper(this.value as NonNullablePart<T>) as NonNullablePart<R>
);
}

return new Maybe<NonNullablePart<R>>(null)
return new Maybe<NonNullablePart<R>>(null);
}

// NOTE: flatMap() "eats" the incomming maybe and will use the value of the current maybe as default
// of the maybe returned by the Mapper()
// you can safely use Maybies in the flatMap() function return value as if it was a direct value
flatMap<U>(
mapper: (value: NonNullable<T>) => U
mapper: (value: NonNullablePart<T>) => U
): NonNullableMaybe<U extends Maybe<infer R> ? Maybe<R> : Maybe<U>> {
type LocalReturn = NonNullableMaybe<U extends Maybe<infer R> ? Maybe<R> : Maybe<U>>
type LocalReturn = NonNullableMaybe<
U extends Maybe<infer R> ? Maybe<R> : Maybe<U>
>;

if (this.value != null) {
const result = mapper(this.value)
const result = mapper(this.value as NonNullablePart<T>);

if (result instanceof Maybe) {
return new Maybe<U>(result.getValue(this.value)) as LocalReturn
return new Maybe<U>(result.getValue(this.value)) as LocalReturn;
}

return new Maybe<U>(result) as LocalReturn
return new Maybe<U>(result) as LocalReturn;
}

return new Maybe<U>(null) as LocalReturn
return new Maybe<U>(null) as LocalReturn;
}

getValue(): T | null | undefined
getValue<D>(defaultValue: D): NonNullable<T> | D
getValue(): T | null | undefined;
getValue<D>(defaultValue: D): NonNullablePart<T> | D;
getValue<D>(defaultValue?: D): T | D | null | undefined {
if (this.value) {
return this.value
return this.value;
}

if (typeof defaultValue !== 'undefined') {
return defaultValue
if (typeof defaultValue !== "undefined") {
return defaultValue;
}
return this.value

return this.value;
}
}

0 comments on commit dcc3c7f

Please sign in to comment.