diff --git a/benchmark/README.md b/benchmark/README.md
index d8eb581..4dcd47d 100644
--- a/benchmark/README.md
+++ b/benchmark/README.md
@@ -5,19 +5,19 @@ Tested on a M1 Pro MacBook Pro.
## Option
```
-fp-ts Option none x 134,775,969 ops/sec ±0.70% (99 runs sampled)
-fp-ts Option some x 95,087,860 ops/sec ±0.26% (97 runs sampled)
-fp-ts Option some chain x 121,914,020 ops/sec ±0.19% (98 runs sampled)
-Boxed Option none x 157,293,978 ops/sec ±0.11% (98 runs sampled)
-Boxed Option some x 204,144,235 ops/sec ±0.27% (94 runs sampled)
-Boxed Option some flatMap x 204,023,130 ops/sec ±0.65% (97 runs sampled)
+fp-ts Option none x 130,955,745 ops/sec ±1.11% (98 runs sampled)
+fp-ts Option some x 97,400,638 ops/sec ±0.26% (100 runs sampled)
+fp-ts Option some chain x 2,776,666 ops/sec ±0.16% (97 runs sampled)
+Boxed Option none x 1,030,036,990 ops/sec ±0.13% (100 runs sampled)
+Boxed Option some x 461,820,063 ops/sec ±0.20% (101 runs sampled)
+Boxed Option some flatMap x 461,736,015 ops/sec ±0.32% (97 runs sampled)
```
## Result
```
-fp-ts Result x 114,543,587 ops/sec ±0.15% (99 runs sampled)
-Boxed Result x 202,457,204 ops/sec ±0.27% (100 runs sampled)
+fp-ts Result x 117,922,535 ops/sec ±0.39% (98 runs sampled)
+Boxed Result x 465,661,918 ops/sec ±0.21% (101 runs sampled)
```
## Future
@@ -25,6 +25,6 @@ Boxed Result x 202,457,204 ops/sec ±0.27% (100 runs sampled)
Careful on the interpretation of the following, as Future doesn't use microtasks and calls its listeners synchronously.
```
-Future x 36,365,914 ops/sec ±0.81% (92 runs sampled)
-Promise x 12,402,743 ops/sec ±0.94% (90 runs sampled)
+Future x 38,364,199 ops/sec ±0.68% (92 runs sampled)
+Promise x 13,482,385 ops/sec ±0.25% (90 runs sampled)
```
diff --git a/src/AsyncData.ts b/src/AsyncData.ts
index 2a2d003..0791feb 100644
--- a/src/AsyncData.ts
+++ b/src/AsyncData.ts
@@ -1,168 +1,113 @@
import { keys, values } from "./Dict";
import { Option, Result } from "./OptionResult";
-import { LooseRecord, Remap } from "./types";
+import { LooseRecord } from "./types";
import { zip } from "./ZipUnzip";
-interface IAsyncData {
+class __AsyncData {
+ static P = {
+ Done: (value: A) => ({ tag: "Done", value }) as const,
+ NotAsked: { tag: "NotAsked" } as const,
+ Loading: { tag: "Loading" } as const,
+ };
/**
- * Returns the AsyncData containing the value from the callback
- *
- * (AsyncData\, A => B) => AsyncData\
- */
- map(this: AsyncData, func: (value: A) => B): AsyncData;
-
- /**
- * Returns the AsyncData containing the value from the callback
- *
- * (AsyncData\, A => AsyncData\) => AsyncData\
- */
- flatMap(
- this: AsyncData,
- func: (value: A) => AsyncData,
- ): AsyncData;
-
- /**
- * Takes a callback taking the Ok value and returning a new result and returns an AsyncData with this new result
- *
- * (AsyncData\>, A => \) => AsyncData\>
- */
- mapOkToResult(
- this: AsyncData>,
- func: (value: A) => Result,
- ): AsyncData>;
-
- /**
- * Takes a callback taking the Ok value and returning a new result and returns an AsyncData with this new result
- *
- * (AsyncData\>, E => \) => AsyncData\>
- */
- mapErrorToResult(
- this: AsyncData>,
- func: (value: E) => Result,
- ): AsyncData>;
-
- /**
- * Takes a callback taking the Ok value and returning a new ok value and returns an AsyncData resolving to this new result
- *
- * (AsyncData\>, A => B) => AsyncData\>
- */
- mapOk(
- this: AsyncData>,
- func: (value: A) => B,
- ): AsyncData>;
-
- /**
- * Takes a callback taking the Error value and returning a new error value and returns an AsyncData to this new result
- *
- * (AsyncData\>, E => B) => AsyncData\>
- */
- mapError(
- this: AsyncData>,
- func: (value: E) => B,
- ): AsyncData>;
-
- /**
- * Takes a callback taking the Ok value and returning an AsyncData
- *
- * (AsyncData\>, A => AsyncData\>) => AsyncData\>
- */
- flatMapOk(
- this: AsyncData>,
- func: (value: A) => AsyncData>,
- ): AsyncData>;
-
- /**
- * Takes a callback taking the Error value and returning an AsyncData
- *
- * (AsyncData\>, E => AsyncData\>) => AsyncData\>
- */
- flatMapError(
- this: AsyncData>,
- func: (value: E) => AsyncData>,
- ): AsyncData>;
-
- /**
- * Return the value if present, and the fallback otherwise
- *
- * (AsyncData\, A) => A
+ * Create an AsyncData.Done value
*/
- getWithDefault(this: AsyncData, defaultValue: A): A;
+ static Done = (value: A): AsyncData => {
+ const asyncData = Object.create(ASYNC_DATA_PROTO) as Done;
+ asyncData.tag = "Done";
+ asyncData.value = value;
+ return asyncData;
+ };
/**
- * Explodes the AsyncData given its case
+ * Create an AsyncData.Loading value
*/
- match(
- this: AsyncData,
- config: {
- Done: (value: A) => B;
- Loading: () => B;
- NotAsked: () => B;
- },
- ): B;
+ static Loading = (): AsyncData => LOADING as Loading;
/**
- * Runs the callback and returns `this`
+ * Create an AsyncData.NotAsked value
*/
- tap(
- this: AsyncData,
- func: (asyncData: AsyncData) => unknown,
- ): AsyncData;
+ static NotAsked = (): AsyncData => NOT_ASKED as NotAsked;
/**
- * Return an option of the value
- *
- * (AsyncData\) => Option\
+ * Turns an array of asyncData into an asyncData of array
*/
- toOption(this: AsyncData): Option;
+ static all = [] | []>(
+ asyncDatas: AsyncDatas,
+ ) => {
+ const length = asyncDatas.length;
+ let acc = AsyncData.Done>([]);
+ let index = 0;
- /**
- * Typeguard
- */
- isDone(this: AsyncData): this is Done;
+ while (true) {
+ if (index >= length) {
+ return acc as AsyncData<{
+ [K in keyof AsyncDatas]: AsyncDatas[K] extends AsyncData
+ ? T
+ : never;
+ }>;
+ }
- /**
- * Typeguard
- */
- isLoading(this: AsyncData): this is Loading;
+ const item = asyncDatas[index];
- /**
- * Typeguard
- */
- isNotAsked(this: AsyncData): this is NotAsked;
-}
+ if (item != null) {
+ acc = acc.flatMap((array) => {
+ return item.map((value) => {
+ array.push(value);
+ return array;
+ });
+ });
+ }
-type Done = Remap> & {
- tag: "Done";
- value: A;
+ index++;
+ }
+ };
/**
- * Returns the value. Use within `if (asyncData.isDone()) { ... }`
+ * Turns an dict of asyncData into a asyncData of dict
*/
- get(this: Done): A;
-};
+ static allFromDict = >>(
+ dict: Dict,
+ ): AsyncData<{
+ [K in keyof Dict]: Dict[K] extends AsyncData ? T : never;
+ }> => {
+ const dictKeys = keys(dict);
-type Loading = Remap> & {
- tag: "Loading";
-};
+ return AsyncData.all(values(dict)).map((values) =>
+ Object.fromEntries(zip(dictKeys, values)),
+ );
+ };
-type NotAsked = Remap> & {
- tag: "NotAsked";
-};
+ static equals = (
+ a: AsyncData,
+ b: AsyncData,
+ equals: (a: A, b: A) => boolean,
+ ) => {
+ return a.tag === "Done" && b.tag === "Done"
+ ? equals(a.value, b.value)
+ : a.tag === b.tag;
+ };
-export type AsyncData = Done | Loading | NotAsked;
+ static isAsyncData = (value: unknown): value is AsyncData =>
+ // @ts-ignore
+ value != null && value.__boxed_type__ === "AsyncData";
-const asyncDataProto = ((): IAsyncData => ({
- map(this: AsyncData, func: (value: A) => B) {
- return this.tag === "Done"
- ? Done(func(this.value))
- : (this as unknown as AsyncData);
- },
+ map(this: AsyncData, func: (value: A) => B): AsyncData {
+ if (this === NOT_ASKED || this === LOADING) {
+ return this as unknown as AsyncData;
+ }
+ return AsyncData.Done(func((this as Done).value));
+ }
- flatMap(this: AsyncData, func: (value: A) => AsyncData) {
- return this.tag === "Done"
- ? func(this.value)
- : (this as unknown as AsyncData);
- },
+ flatMap(
+ this: AsyncData,
+ func: (value: A) => AsyncData,
+ ): AsyncData {
+ if (this === NOT_ASKED || this === LOADING) {
+ return this as unknown as AsyncData;
+ }
+ return func((this as Done).value);
+ }
/**
* For AsyncData>:
@@ -179,7 +124,7 @@ const asyncDataProto = ((): IAsyncData => ({
Error: () => value as unknown as Result,
});
});
- },
+ }
/**
* For AsyncData>:
@@ -196,7 +141,7 @@ const asyncDataProto = ((): IAsyncData => ({
Ok: () => value as unknown as Result,
});
});
- },
+ }
/**
* For AsyncData>:
@@ -213,7 +158,7 @@ const asyncDataProto = ((): IAsyncData => ({
Error: () => value as unknown as Result,
});
});
- },
+ }
/**
* For AsyncData>:
@@ -231,7 +176,7 @@ const asyncDataProto = ((): IAsyncData => ({
Error: (error) => Result.Error(func(error)),
});
});
- },
+ }
/**
* For AsyncData>:
@@ -245,10 +190,10 @@ const asyncDataProto = ((): IAsyncData => ({
return this.flatMap((value) => {
return value.match({
Ok: (value) => func(value) as AsyncData>,
- Error: () => Done(value as unknown as Result),
+ Error: () => AsyncData.Done(value as unknown as Result),
});
});
- },
+ }
/**
* For AsyncData>:
@@ -261,15 +206,25 @@ const asyncDataProto = ((): IAsyncData => ({
): AsyncData> {
return this.flatMap((value) => {
return value.match({
- Ok: () => Done(value as unknown as Result),
+ Ok: () => AsyncData.Done(value as unknown as Result),
Error: (error) => func(error) as AsyncData>,
});
});
- },
+ }
- getWithDefault(this: AsyncData, defaultValue: A) {
- return this.tag === "Done" ? this.value : defaultValue;
- },
+ /**
+ * Returns the value. Use within `if (asyncData.isDone()) { ... }`
+ */
+ get(this: Done) {
+ return this.value;
+ }
+
+ getWithDefault(this: AsyncData, defaultValue: A): A {
+ if (this === NOT_ASKED || this === LOADING) {
+ return defaultValue;
+ }
+ return (this as Done).value;
+ }
match(
this: AsyncData,
@@ -278,148 +233,73 @@ const asyncDataProto = ((): IAsyncData => ({
Loading: () => B;
NotAsked: () => B;
},
- ) {
- return this.tag === "Done"
- ? config.Done(this.value)
- : this.tag === "Loading"
- ? config.Loading()
- : config.NotAsked();
- },
+ ): B {
+ if (this === NOT_ASKED) {
+ return config.NotAsked();
+ }
+ if (this === LOADING) {
+ return config.Loading();
+ }
+ return config.Done((this as Done).value);
+ }
tap(this: AsyncData, func: (asyncData: AsyncData) => unknown) {
func(this);
return this;
- },
+ }
- toOption(this: AsyncData) {
- return this.tag === "Done" ? Option.Some(this.value) : Option.None();
- },
+ toOption(this: AsyncData): Option {
+ if (this === NOT_ASKED || this === LOADING) {
+ return Option.None();
+ }
+ return Option.Some((this as Done).value);
+ }
- isDone(this: AsyncData): boolean {
- return this.tag === "Done";
- },
+ isDone(this: AsyncData): this is Done {
+ return this !== NOT_ASKED && this !== LOADING;
+ }
- isLoading(this: AsyncData): boolean {
- return this.tag === "Loading";
- },
+ isLoading(this: AsyncData): this is Loading {
+ return this === LOADING;
+ }
- isNotAsked(this: AsyncData): boolean {
- return this.tag === "NotAsked";
- },
-}))();
+ isNotAsked(this: AsyncData): this is NotAsked {
+ return this === NOT_ASKED;
+ }
+}
// @ts-expect-error
-asyncDataProto.__boxed_type__ = "AsyncData";
+__AsyncData.prototype.__boxed_type__ = "AsyncData";
-const doneProto = ((): Omit, "tag" | "value"> => ({
- ...(asyncDataProto as IAsyncData),
-
- get() {
- return this.value;
- },
-}))();
-
-const Done = (value: A): AsyncData => {
- const asyncData = Object.create(doneProto) as Done;
- asyncData.tag = "Done";
- asyncData.value = value;
- return asyncData;
-};
+const ASYNC_DATA_PROTO = Object.create(
+ null,
+ Object.getOwnPropertyDescriptors(__AsyncData.prototype),
+);
const LOADING = (() => {
- const asyncData = Object.create(asyncDataProto) as Loading;
+ const asyncData = Object.create(ASYNC_DATA_PROTO) as Loading;
asyncData.tag = "Loading";
return asyncData;
})();
const NOT_ASKED = (() => {
- const asyncData = Object.create(asyncDataProto) as NotAsked;
+ const asyncData = Object.create(ASYNC_DATA_PROTO) as NotAsked;
asyncData.tag = "NotAsked";
return asyncData;
})();
-const Loading = (): AsyncData => LOADING as Loading;
-const NotAsked = (): AsyncData => NOT_ASKED as NotAsked;
-
-const asyncDataPattern = {
- Done: (value: A) => ({ tag: "Done", value } as const),
- NotAsked: { tag: "NotAsked" } as const,
- Loading: { tag: "Loading" } as const,
-};
-
-export const AsyncData = {
- /**
- * Create an AsyncData.Done value
- */
- Done,
-
- /**
- * Create an AsyncData.Loading value
- */
- Loading,
-
- /**
- * Create an AsyncData.NotAsked value
- */
- NotAsked,
-
- /**
- * Turns an array of asyncData into an asyncData of array
- */
- all[] | []>(asyncDatas: AsyncDatas) {
- const length = asyncDatas.length;
- let acc = AsyncData.Done>([]);
- let index = 0;
-
- while (true) {
- if (index >= length) {
- return acc as AsyncData<{
- [K in keyof AsyncDatas]: AsyncDatas[K] extends AsyncData
- ? T
- : never;
- }>;
- }
-
- const item = asyncDatas[index];
-
- if (item != null) {
- acc = acc.flatMap((array) => {
- return item.map((value) => {
- array.push(value);
- return array;
- });
- });
- }
-
- index++;
- }
- },
-
- /**
- * Turns an dict of asyncData into a asyncData of dict
- */
- allFromDict>>(
- dict: Dict,
- ): AsyncData<{
- [K in keyof Dict]: Dict[K] extends AsyncData ? T : never;
- }> {
- const dictKeys = keys(dict);
-
- return AsyncData.all(values(dict)).map((values) =>
- Object.fromEntries(zip(dictKeys, values)),
- );
- },
+interface Done extends __AsyncData {
+ tag: "Done";
+ value: A;
+}
- equals(a: AsyncData, b: AsyncData, equals: (a: A, b: A) => boolean) {
- return a.tag === "Done" && b.tag === "Done"
- ? equals(a.value, b.value)
- : a.tag === b.tag;
- },
+interface Loading extends __AsyncData {
+ tag: "Loading";
+}
- isAsyncData: (value: unknown): value is AsyncData =>
- value != null &&
- (Object.prototype.isPrototypeOf.call(doneProto, value) ||
- Object.prototype.isPrototypeOf.call(asyncDataProto, value)),
+interface NotAsked extends __AsyncData {
+ tag: "NotAsked";
+}
- P: asyncDataPattern,
-};
+export const AsyncData = __AsyncData;
+export type AsyncData = Done | Loading | NotAsked;
diff --git a/src/Future.ts b/src/Future.ts
index 95a9128..2733354 100644
--- a/src/Future.ts
+++ b/src/Future.ts
@@ -3,24 +3,34 @@ import { Result } from "./OptionResult";
import { LooseRecord } from "./types";
import { zip } from "./ZipUnzip";
-export class Future {
+export class __Future {
/**
* Creates a new future from its initializer function (like `new Promise(...)`)
*/
static make = (
init: (resolver: (value: A) => void) => (() => void) | void,
): Future => {
- return new Future(init);
+ const future = Object.create(FUTURE_PROTO) as Future;
+ const resolver = (value: A) => {
+ if (future._state.tag === "Pending") {
+ const resolveCallbacks = future._state.resolveCallbacks;
+ future._state = { tag: "Resolved", value };
+ resolveCallbacks?.forEach((func) => func(value));
+ }
+ };
+ future._state = { tag: "Pending" };
+ future._state.cancel = init(resolver);
+ return future as Future;
};
static isFuture = (value: unknown): value is Future =>
- value != null && Object.prototype.isPrototypeOf.call(futureProto, value);
+ value != null && Object.prototype.isPrototypeOf.call(FUTURE_PROTO, value);
/**
* Creates a future resolved to the passed value
*/
static value = (value: A): Future => {
- const future = Object.create(futureProto);
+ const future = Object.create(FUTURE_PROTO);
future._state = { tag: "Resolved", value };
return future as Future;
};
@@ -98,19 +108,8 @@ export class Future {
| { tag: "Cancelled" }
| { tag: "Resolved"; value: A };
- protected constructor(
- init: (resolver: (value: A) => void) => (() => void) | void,
- ) {
- const resolver = (value: A) => {
- if (this._state.tag === "Pending") {
- const resolveCallbacks = this._state.resolveCallbacks;
- this._state = { tag: "Resolved", value };
- resolveCallbacks?.forEach((func) => func(value));
- }
- };
-
+ protected constructor() {
this._state = { tag: "Pending" };
- this._state.cancel = init(resolver);
}
/**
@@ -145,7 +144,10 @@ export class Future {
const { cancel, cancelCallbacks } = this._state;
// We have to set the future as cancelled first to avoid an infinite loop
this._state = { tag: "Cancelled" };
- cancel?.();
+ if (cancel != undefined) {
+ // @ts-ignore Compiler doesn't like that `cancel` is potentially `void`
+ cancel();
+ }
cancelCallbacks?.forEach((func) => func());
}
}
@@ -390,7 +392,10 @@ export class Future {
}
}
-const futureProto = Object.create(
+const FUTURE_PROTO = Object.create(
null,
- Object.getOwnPropertyDescriptors(Future.prototype),
+ Object.getOwnPropertyDescriptors(__Future.prototype),
);
+
+export const Future = __Future;
+export type Future = __Future;
diff --git a/src/OptionResult.ts b/src/OptionResult.ts
index 4460ff1..569f606 100644
--- a/src/OptionResult.ts
+++ b/src/OptionResult.ts
@@ -1,221 +1,53 @@
import { keys, values } from "./Dict";
-import { LooseRecord, Remap } from "./types";
+import { LooseRecord } from "./types";
import { zip } from "./ZipUnzip";
-interface IOption {
- /**
- * Returns the Option containing the value from the callback
- *
- * (Option\, A => B) => Option\
- */
- map(this: Option, func: (value: A) => B): Option;
-
- /**
- * Returns the Option containing the value from the callback
- *
- * (Option\, A => Option\) => Option\
- */
- flatMap(this: Option, func: (value: A) => Option): Option;
-
- /**
- * Returns the Option if its value matches the predicate, otherwise false
- *
- * (Option\, A => boolean) => Option\
- */
- filter(
- this: Option,
- func: (value: A) => value is B,
- ): Option;
- filter(this: Option, func: (value: A) => boolean): Option;
-
- /**
- * Return the value if present, and the fallback otherwise
- *
- * (Option\, A) => A
- */
- getWithDefault(this: Option, defaultValue: A): A;
-
- /**
- * Explodes the Option given its case
- */
- match(
- this: Option,
- config: { Some: (value: A) => B; None: () => B },
- ): B;
-
- /**
- * Runs the callback and returns `this`
- */
- tap(this: Option, func: (option: Option) => unknown): Option;
-
- /**
- * Converts the Option\ to a `A | undefined`
- */
- toUndefined(this: Option): A | undefined;
-
- /**
- * Converts the Option\ to a `A | null`
- */
- toNull(this: Option): A | null;
+class __Option {
+ static P = {
+ Some: (value: A) => ({ tag: "Some", value }) as const,
+ None: { tag: "None" } as const,
+ };
- /**
- * Takes the option and turns it into Ok(value) is Some, or Error(valueWhenNone)
- */
- toResult(this: Option, valueWhenNone: E): Result;
+ static Some = (value: A): Option => {
+ const option = Object.create(OPTION_PROTO) as Some;
+ option.tag = "Some";
+ option.value = value;
+ return option;
+ };
- /**
- * Typeguard
- */
- isSome(this: Option): this is Some;
-
- /**
- * Typeguard
- */
- isNone(this: Option): this is None;
-}
+ static None = (): Option => NONE as None;
-type Some = Remap> & {
- tag: "Some";
- value: A;
-
- /**
- * Returns the value. Use within `if (option.isSome()) { ... }`
- */
- get(this: Some): A;
-};
-
-type None = Remap> & {
- tag: "None";
-};
-
-export type Option = Some | None;
-
-const optionProto = ((): IOption => ({
- map(this: Option, func: (value: A) => B) {
- return this.tag === "Some"
- ? Some(func(this.value))
- : (this as unknown as Option);
- },
-
- flatMap(this: Option, func: (value: A) => Option) {
- return this.tag === "Some"
- ? func(this.value)
- : (this as unknown as Option);
- },
-
- filter(this: Option, func: (value: A) => boolean) {
- return this.tag === "Some" && func(this.value) ? this : Option.None();
- },
-
- getWithDefault(this: Option, defaultValue: A) {
- return this.tag === "Some" ? this.value : defaultValue;
- },
-
- match(this: Option, config: { Some: (value: A) => B; None: () => B }) {
- return this.tag === "Some" ? config.Some(this.value) : config.None();
- },
-
- tap(this: Option, func: (option: Option) => unknown) {
- func(this);
- return this;
- },
-
- toUndefined(this: Option) {
- return this.tag === "Some" ? this.value : undefined;
- },
-
- toNull(this: Option) {
- return this.tag === "Some" ? this.value : null;
- },
-
- toResult(this: Option, valueWhenNone: E): Result {
- return this.match({
- Some: (ok) => Result.Ok(ok),
- None: () => Result.Error(valueWhenNone),
- });
- },
-
- isSome(this: Option): boolean {
- return this.tag === "Some";
- },
-
- isNone(this: Option): boolean {
- return this.tag === "None";
- },
-}))();
-
-// @ts-expect-error
-optionProto.__boxed_type__ = "Option";
-
-const someProto = ((): Omit, "tag" | "value"> => ({
- ...(optionProto as IOption),
-
- get() {
- return this.value;
- },
-}))();
-
-const Some = (value: A): Option => {
- const option = Object.create(someProto) as Some;
- option.tag = "Some";
- option.value = value;
- return option;
-};
-
-const NONE = (() => {
- const option = Object.create(optionProto) as None;
- option.tag = "None";
- return option;
-})();
-
-const None = (): Option => NONE as None;
-
-const optionPattern = {
- Some: (value: A) => ({ tag: "Some", value } as const),
- None: { tag: "None" } as const,
-};
-
-export const Option = {
- /**
- * Create an Option.Some value
- */
- Some,
-
- /**
- * Create an Option.None value
- */
- None,
-
- isOption: (value: unknown): value is Option =>
- value != null &&
- (Object.prototype.isPrototypeOf.call(optionProto, value) ||
- Object.prototype.isPrototypeOf.call(someProto, value)),
+ static isOption = (value: unknown): value is Option =>
+ // @ts-ignore
+ value != null && value.__boxed_type__ === "Option";
/**
* Create an Option from a nullable value
*/
- fromNullable(nullable: A | null | undefined): Option {
- return nullable == null ? None() : Some(nullable);
- },
+ static fromNullable = (nullable: A | null | undefined): Option => {
+ return nullable == null ? (NONE as None) : Option.Some(nullable);
+ };
/**
* Create an Option from a value | null
*/
- fromNull(nullable: A | null): Option {
- return nullable === null ? None() : Some(nullable);
- },
+ static fromNull = (nullable: A | null): Option => {
+ return nullable === null ? (NONE as None) : Option.Some(nullable);
+ };
/**
* Create an Option from a undefined | value
*/
- fromUndefined(nullable: A | undefined): Option {
- return nullable === undefined ? None() : Some(nullable);
- },
+ static fromUndefined = (nullable: A | undefined): Option => {
+ return nullable === undefined
+ ? (NONE as None)
+ : Option.Some(nullable);
+ };
/**
* Turns an array of options into an option of array
*/
- all[] | []>(options: Options) {
+ static all = [] | []>(options: Options) => {
const length = options.length;
let acc = Option.Some>([]);
let index = 0;
@@ -240,302 +72,247 @@ export const Option = {
index++;
}
- },
+ };
/**
* Turns an dict of options into a options of dict
*/
- allFromDict>>(
+ static allFromDict = >>(
dict: Dict,
): Option<{
[K in keyof Dict]: Dict[K] extends Option ? T : never;
- }> {
+ }> => {
const dictKeys = keys(dict);
- return this.all(values(dict)).map((values) =>
+ return Option.all(values(dict)).map((values) =>
Object.fromEntries(zip(dictKeys, values)),
);
- },
+ };
- equals(
+ static equals = (
a: Option,
b: Option,
equals: (a: A, b: A) => boolean,
- ): boolean {
- return a.tag === "Some" && b.tag === "Some"
- ? equals(a.value, b.value)
+ ): boolean => {
+ return a.isSome() && b.isSome()
+ ? equals(a.get(), b.get())
: a.tag === b.tag;
- },
-
- P: optionPattern,
-};
+ };
-interface IResult {
/**
- * Returns the Result containing the value from the callback
+ * Returns the Option containing the value from the callback
*
- * (Result\, A => B) => Result\
+ * (Option\, A => B) => Option\
*/
- map(this: Result, func: (value: A) => B): Result;
+ map(this: Option, func: (value: A) => B): Option {
+ if (this === NONE) {
+ return this as unknown as Option;
+ }
+ return Option.Some(func((this as Some).value));
+ }
/**
- * Returns the Result containing the error returned from the callback
+ * Returns the Option containing the value from the callback
*
- * (Result\, E => F) => Result\
+ * (Option\, A => Option\) => Option\
*/
- mapError(this: Result, func: (value: E) => F): Result;
+ flatMap(this: Option, func: (value: A) => Option): Option {
+ if (this === NONE) {
+ return this as unknown as Option;
+ }
+ return func((this as Some).value);
+ }
/**
- * Returns the Result containing the value from the callback
+ * Returns the Option if its value matches the predicate, otherwise false
*
- * (Result\, A => Result\) => Result\
+ * (Option\, A => boolean) => Option\
*/
- flatMap(
- this: Result,
- func: (value: A) => Result,
- ): Result;
+ filter(
+ this: Option,
+ func: (value: A) => value is B,
+ ): Option;
+ filter(this: Option, func: (value: A) => boolean): Option;
+ filter(this: Option, func: (value: A) => boolean): Option {
+ if (this === NONE) {
+ return this as unknown as Option;
+ }
+ return func((this as Some).value) ? this : (NONE as None);
+ }
/**
- * Returns the Result containing the value from the callback
- *
- * (Result\, E => Result\) => Result\
+ * Returns the value. Use within `if (Option.isSome()) { ... }`
*/
- flatMapError(
- this: Result,
- func: (value: E) => Result,
- ): Result;
-
+ get(this: Some) {
+ return this.value;
+ }
/**
* Return the value if present, and the fallback otherwise
*
- * (Result\, A) => A
+ * (Option\, A) => A
*/
- getWithDefault(this: Result, defaultValue: A): A;
+ getWithDefault(this: Option, defaultValue: A): A {
+ if (this === NONE) {
+ return defaultValue;
+ }
+ return (this as Some).value;
+ }
/**
- * Explodes the Result given its case
+ * Explodes the Option given its case
*/
match(
- this: Result,
- config: { Ok: (value: A) => B; Error: (error: E) => B },
- ): B;
+ this: Option,
+ config: { Some: (value: A) => B; None: () => B },
+ ): B {
+ if (this === NONE) {
+ return config.None();
+ }
+ return config.Some((this as Some).value);
+ }
/**
* Runs the callback and returns `this`
*/
- tap(
- this: Result,
- func: (result: Result) => unknown,
- ): Result;
+ tap(this: Option, func: (option: Option) => unknown): Option {
+ func(this);
+ return this;
+ }
/**
- * Runs the callback if ok and returns `this`
+ * Converts the Option\ to a `A | undefined`
*/
- tapOk(this: Result, func: (value: A) => unknown): Result;
+ toUndefined(this: Option): A | undefined {
+ if (this === NONE) {
+ return undefined;
+ }
+ return (this as Some).value;
+ }
/**
- * Runs the callback if error and returns `this`
+ * Converts the Option\ to a `A | null`
*/
- tapError(this: Result, func: (error: E) => unknown): Result;
+ toNull(this: Option): A | null {
+ if (this === NONE) {
+ return null;
+ }
+ return (this as Some).value;
+ }
/**
- * Return an option of the value
- *
- * (Result\) => Option\
+ * Takes the option and turns it into Ok(value) is Some, or Error(valueWhenNone)
*/
- toOption(this: Result): Option;
+ toResult(this: Option, valueWhenNone: E): Result {
+ return this.match({
+ Some: (ok) => Result.Ok(ok),
+ None: () => Result.Error(valueWhenNone),
+ });
+ }
/**
* Typeguard
*/
- isOk(this: Result): this is Ok;
+ isSome(this: Option): this is Some {
+ return this !== NONE;
+ }
/**
* Typeguard
*/
- isError(this: Result): this is Error;
+ isNone(this: Option): this is None {
+ return this === NONE;
+ }
}
-type Ok = Remap> & {
- tag: "Ok";
- value: A;
-
- /**
- * Returns the ok value. Use within `if (result.isOk()) { ... }`
- */
- get(this: Ok): A;
-};
-
-type Error = Remap> & {
- tag: "Error";
- value: E;
-
- /**
- * Returns the error value. Use within `if (result.isError()) { ... }`
- */
- getError(this: Error): E;
-};
-
-export type Result = Ok | Error;
-
-const resultProto = ((): IResult => ({
- map(this: Result, func: (value: A) => B) {
- return this.tag === "Ok"
- ? Ok(func(this.value))
- : (this as unknown as Result);
- },
-
- mapError(this: Result, func: (value: E) => F) {
- return this.tag === "Ok"
- ? (this as unknown as Result)
- : Error(func(this.value));
- },
-
- flatMap(this: Result, func: (value: A) => Result) {
- return this.tag === "Ok"
- ? func(this.value)
- : (this as unknown as Result);
- },
-
- flatMapError(this: Result, func: (value: E) => Result) {
- return this.tag === "Ok"
- ? (this as unknown as Result)
- : func(this.value);
- },
-
- getWithDefault(this: Result, defaultValue: A) {
- return this.tag === "Ok" ? this.value : defaultValue;
- },
-
- match(
- this: Result,
- config: { Ok: (value: A) => B; Error: (error: E) => B },
- ) {
- return this.tag === "Ok" ? config.Ok(this.value) : config.Error(this.value);
- },
-
- tap(this: Result, func: (result: Result) => unknown) {
- func(this);
- return this;
- },
-
- tapOk(this: Result, func: (value: A) => unknown) {
- if (this.tag === "Ok") {
- func(this.value);
- }
- return this;
- },
-
- tapError(this: Result, func: (error: E) => unknown) {
- if (this.tag === "Error") {
- func(this.value);
- }
- return this;
- },
-
- toOption(this: Result) {
- return this.tag === "Ok" ? Some(this.value) : None();
- },
-
- isOk(this: Result): boolean {
- return this.tag === "Ok";
- },
-
- isError(this: Result): boolean {
- return this.tag === "Error";
- },
-}))();
-
// @ts-expect-error
-resultProto.__boxed_type__ = "Result";
+__Option.prototype.__boxed_type__ = "Option";
-const okProto = ((): Omit, "tag" | "value"> => ({
- ...(resultProto as IResult),
+const OPTION_PROTO = Object.create(
+ null,
+ Object.getOwnPropertyDescriptors(__Option.prototype),
+);
- get() {
- return this.value;
- },
-}))();
-
-const errorProto = ((): Omit, "tag" | "value"> => ({
- ...(resultProto as IResult),
+const NONE = (() => {
+ const option = Object.create(OPTION_PROTO) as None;
+ option.tag = "None";
+ return option;
+})();
- getError() {
- return this.value;
- },
-}))();
+interface Some extends __Option {
+ tag: "Some";
+ value: A;
+}
-const Ok = (value: A): Result => {
- const result = Object.create(okProto) as Ok;
- result.tag = "Ok";
- result.value = value;
- return result;
-};
+interface None extends __Option {
+ tag: "None";
+}
-const Error = (value: E): Result => {
- const result = Object.create(errorProto) as Error;
- result.tag = "Error";
- result.value = value;
- return result;
-};
+export const Option = __Option;
+export type Option = Some | None;
-const resultPattern = {
- Ok: (value: A) => ({ tag: "Ok", value } as const),
- Error: (value: E) => ({ tag: "Error", value } as const),
-};
+class __Result {
+ static P = {
+ Ok: (value: A) => ({ tag: "Ok", value }) as const,
+ Error: (error: E) => ({ tag: "Error", error }) as const,
+ };
-export const Result = {
- /**
- * Create an Result.Ok value
- */
- Ok,
+ static Ok =