Skip to content

Commit

Permalink
feat: Improve performance when creating millions of Results
Browse files Browse the repository at this point in the history
  • Loading branch information
Olian04 committed Feb 12, 2024
1 parent 24bfc08 commit 03c54ed
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 61 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

# simply-result

Simply typesafe Result and Option monads in typescript and javascript. Only ~800b minified and gzipped. Branchless implementation, waisting no processing cycles on unnecessary operations. On average [~13% faster than try-catch](#performance).
Simply typesafe Result and Option monads in typescript and javascript. Only ~700b minified and gzipped. Branchless implementation, waisting no processing cycles on unnecessary operations. Low memory foot print and on average [~13% faster than try-catch](#performance).

See also the sister library [simply-result-util](https://github.com/Olian04/simply-result-util) for useful monadic helper functions such as `Try`, `transpose`, and `flatten`.

Expand Down
2 changes: 1 addition & 1 deletion assets/size.badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 42 additions & 20 deletions src/Option.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err, Ok } from "./Result";
import { Err, ErrImpl, Ok, OkImpl } from "./Result";

export type Option<V> =
| Some<V>
Expand Down Expand Up @@ -37,31 +37,53 @@ export interface None {
toString(): string;
}

export const Some = <V>(value: V): Some<V> => {
const self = {
some: value,
isSome: true as const,
isNone: false as const,
match: cases => cases.Some(value),
intoResult: () => Ok(value),
map: fn => Some(fn(value)),
filter: fn => fn(value) ? self : None,
andThen: fn => fn(value),
elseThen: () => self,
unwrapOr: () => value,
unwrapElse: () => value,
toString: () => `Some(${value})`,
};
return self;
};
export class SomeImpl<V> implements Some<V> {
isSome = true as const;
isNone = false as const;
constructor(
public some: V,
) {}

intoResult(error: unknown): Ok<V> {
return new OkImpl(this.some);
}
filter(fn: (some: V) => boolean): Option<V> {
if (fn(this.some)) {
return this;
}
return None;
}
match<T>(cases: { Some: (some: V) => T; }): T {
return cases.Some(this.some);
}
map<T>(fn: (some: V) => T): Some<T> {
return new SomeImpl(fn(this.some));
}
andThen<T>(fn: (some: V) => T): T {
return fn(this.some);
}
elseThen(fn: unknown): Some<V> {
return this;
}
unwrapOr(some: unknown): V {
return this.some;
}
unwrapElse(fn: unknown): V {
return this.some;
}
toString(): string {
return `Some(${this.some})`;
}
}

export const Some = <V>(value: V): Some<V> => new SomeImpl(value);
export const None: None = {
isSome: false as const,
isNone: true as const,
match: cases => cases.None(),
intoResult: error => Err(error),
map: () => None,
intoResult: err => new ErrImpl(err),
filter: () => None,
map: () => None,
andThen: () => None,
elseThen: fn => fn(),
unwrapOr: some => some,
Expand Down
117 changes: 80 additions & 37 deletions src/Result.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { None, Some } from "./Option";
import { None, Some, SomeImpl } from "./Option";

export type Result<V, E = Error> =
| Ok<V>
Expand Down Expand Up @@ -40,40 +40,83 @@ export interface Err<E> {
toString(): string;
}

export const Ok = <V>(value: V): Ok<V> => {
const self = {
ok: value,
isOk: true as const,
isErr: false as const,
match: cases => cases.Ok(value),
intoOption: () => Some(value),
intoErrOption: () => None,
map: fn => Ok(fn(value)),
mapErr: () => self,
andThen: fn => fn(value),
elseThen: () => self,
unwrapOr: () => value,
unwrapElse: () => value,
toString: () => `Ok(${value})`,
};
return self;
};
export class OkImpl<V> implements Ok<V> {
isOk = true as const;
isErr = false as const;
constructor(
public ok: V,
) {}

export const Err = <E>(error: E): Err<E> => {
const self = {
err: error,
isOk: false as const,
isErr: true as const,
match: cases => cases.Err(error),
intoOption: () => None,
intoErrOption: () => Some(error),
map: () => self,
mapErr: fn => Err(fn(error)),
andThen: () => self,
elseThen: fn => fn(error),
unwrapOr: ok => ok,
unwrapElse: fn => fn(error),
toString: () => `Err(${error})`,
};
return self;
};
intoOption(): Some<V> {
return new SomeImpl(this.ok);
}
intoErrOption(): None {
return None;
}
match<T>(cases: { Ok: (ok: V) => T; }): T {
return cases.Ok(this.ok);
}
map<T>(fn: (ok: V) => T): Ok<T> {
return new OkImpl(fn(this.ok));
}
mapErr(fn: unknown): Ok<V> {
return this;
}
andThen<T>(fn: (ok: V) => T): T {
return fn(this.ok);
}
elseThen(fn: unknown): Ok<V> {
return this;
}
unwrapOr(ok: unknown): V {
return this.ok;
}
unwrapElse(fn: unknown): V {
return this.ok;
}
toString(): string {
return `Ok(${this.ok})`;
}
}

export class ErrImpl<E> implements Err<E> {
isOk = false as const;
isErr = true as const;
constructor(
public err: E,
) {}

intoOption(): None {
return None;
}
intoErrOption(): Some<E> {
return new SomeImpl(this.err);
}
match<T>(cases: { Err: (err: E) => T; }): T {
return cases.Err(this.err);
}
map(fn: unknown): Err<E> {
return this;
}
mapErr<F>(fn: (err: E) => F): Err<F> {
return new ErrImpl(fn(this.err));
}
andThen(fn: unknown): Err<E> {
return this;
}
elseThen<T>(fn: (err: E) => T): T {
return fn(this.err);
}
unwrapOr<V>(ok: V): V {
return ok;
}
unwrapElse<V>(fn: (err: E) => V): V {
return fn(this.err);
}
toString(): string {
return `Err(${this.err})`;
}
}

export const Ok = <V>(value: V): Ok<V> => new OkImpl(value);
export const Err = <E>(error: E): Err<E> => new ErrImpl(error);
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './Option';
export * from './Result';
export { None, Some, Option } from './Option';
export { Err, Ok, Result } from './Result';

0 comments on commit 03c54ed

Please sign in to comment.