Skip to content

Commit

Permalink
Moved Option/Result discriminated union to symbols. Fixed a bug in ma…
Browse files Browse the repository at this point in the history
…tch that came to light with this change.
  • Loading branch information
mwilson committed Feb 24, 2022
1 parent 922d70a commit 77ed970
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 50 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "oxide.ts",
"version": "0.9.3",
"version": "0.9.4",
"description": "Rust's Option<T> and Result<T, E>, implemented for TypeScript.",
"main": "dist",
"types": "dist",
Expand Down
7 changes: 5 additions & 2 deletions src/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,11 @@ function matches<T>(cond: BranchCondition<T> | Default, val: T): boolean {
: (cond as (val: T | Default) => boolean)(val);
}

if ((Option.is(cond) || Result.is(cond)) && cond.is(val)) {
return matches(cond.unwrap_unchecked(), val.unwrap_unchecked());
if (Option.is(cond) || Result.is(cond)) {
return (
cond.is(val) &&
matches(cond.unwrap_unchecked(), val.unwrap_unchecked())
);
}

if (
Expand Down
44 changes: 23 additions & 21 deletions src/monad/option.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Result, Ok, Err } from "./result";

const IsSome = Symbol("IsSome");

export type Option<T> = Some<T> | None<T>;
export type Some<T> = OptionType<T> & { __IsSome__: true };
export type None<T> = OptionType<T> & { __IsSome__: false };
export type Some<T> = OptionType<T> & { [IsSome]: true };
export type None<T> = OptionType<T> & { [IsSome]: false };

class OptionType<T> {
private val: T;
readonly __IsSome__: boolean;
readonly [IsSome]: boolean;

constructor(val: T, some: boolean) {
this.val = val;
this.__IsSome__ = some;
this[IsSome] = some;
Object.freeze(this);
}

Expand All @@ -28,7 +30,7 @@ class OptionType<T> {
* ```
*/
is(cmp: unknown): cmp is Option<unknown> {
return cmp instanceof OptionType && this.__IsSome__ === cmp.__IsSome__;
return cmp instanceof OptionType && this[IsSome] === cmp[IsSome];
}

/**
Expand All @@ -47,7 +49,7 @@ class OptionType<T> {
* ```
*/
eq(cmp: Option<T>): boolean {
return this.__IsSome__ === cmp.__IsSome__ && this.val === cmp.val;
return this[IsSome] === cmp[IsSome] && this.val === cmp.val;
}

/**
Expand All @@ -66,7 +68,7 @@ class OptionType<T> {
* ```
*/
neq(cmp: Option<T>): boolean {
return this.__IsSome__ !== cmp.__IsSome__ || this.val !== cmp.val;
return this[IsSome] !== cmp[IsSome] || this.val !== cmp.val;
}

/**
Expand All @@ -82,7 +84,7 @@ class OptionType<T> {
* ```
*/
is_some(): this is Some<T> {
return this.__IsSome__;
return this[IsSome];
}

/**
Expand All @@ -98,7 +100,7 @@ class OptionType<T> {
* ```
*/
is_none(): this is None<never> {
return !this.__IsSome__;
return !this[IsSome];
}

/**
Expand All @@ -116,7 +118,7 @@ class OptionType<T> {
* ```
*/
expect(msg: string): T {
if (this.__IsSome__) {
if (this[IsSome]) {
return this.val;
} else {
throw new Error(msg);
Expand Down Expand Up @@ -157,7 +159,7 @@ class OptionType<T> {
* ```
*/
unwrap_or(def: T): T {
return this.__IsSome__ ? this.val : def;
return this[IsSome] ? this.val : def;
}

/**
Expand All @@ -172,7 +174,7 @@ class OptionType<T> {
* ```
*/
unwrap_or_else(f: () => T): T {
return this.__IsSome__ ? this.val : f();
return this[IsSome] ? this.val : f();
}

/**
Expand Down Expand Up @@ -210,7 +212,7 @@ class OptionType<T> {
* ```
*/
or(optb: Option<T>): Option<T> {
return this.__IsSome__ ? this : optb;
return this[IsSome] ? this : optb;
}

/**
Expand All @@ -227,7 +229,7 @@ class OptionType<T> {
* ```
*/
or_else(f: () => Option<T>): Option<T> {
return this.__IsSome__ ? this : f();
return this[IsSome] ? this : f();
}

/**
Expand All @@ -248,7 +250,7 @@ class OptionType<T> {
* ```
*/
and<U>(optb: Option<U>): Option<U> {
return this.__IsSome__ ? optb : None;
return this[IsSome] ? optb : None;
}

/**
Expand All @@ -270,7 +272,7 @@ class OptionType<T> {
* ```
*/
and_then<U>(f: (val: T) => Option<U>): Option<U> {
return this.__IsSome__ ? f(this.val) : None;
return this[IsSome] ? f(this.val) : None;
}

/**
Expand All @@ -284,7 +286,7 @@ class OptionType<T> {
* ```
*/
map<U>(f: (val: T) => U): Option<U> {
return this.__IsSome__ ? new OptionType(f(this.val), true) : None;
return this[IsSome] ? new OptionType(f(this.val), true) : None;
}

/**
Expand All @@ -305,7 +307,7 @@ class OptionType<T> {
* ```
*/
map_or<U>(def: U, f: (val: T) => U): U {
return this.__IsSome__ ? f(this.val) : def;
return this[IsSome] ? f(this.val) : def;
}

/**
Expand All @@ -322,7 +324,7 @@ class OptionType<T> {
* ```
*/
map_or_else<U>(def: () => U, f: (val: T) => U): U {
return this.__IsSome__ ? f(this.val) : def();
return this[IsSome] ? f(this.val) : def();
}

/**
Expand All @@ -342,7 +344,7 @@ class OptionType<T> {
* ```
*/
ok_or<E>(err: E): Result<T, E> {
return this.__IsSome__ ? Ok<T, E>(this.val) : Err<E, T>(err);
return this[IsSome] ? Ok<T, E>(this.val) : Err<E, T>(err);
}

/**
Expand All @@ -362,7 +364,7 @@ class OptionType<T> {
* ```
*/
ok_or_else<E>(f: () => E): Result<T, E> {
return this.__IsSome__ ? Ok<T, E>(this.val) : Err<E, T>(f());
return this[IsSome] ? Ok<T, E>(this.val) : Err<E, T>(f());
}
}

Expand Down
52 changes: 26 additions & 26 deletions src/monad/result.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Option, Some, None } from "./option";

const IsOk = Symbol("IsOk");

export type Result<T, E> = Ok<T, E> | Err<E, T>;
export type Ok<T, E> = ResultType<T, E> & { __IsOk__: true };
export type Err<E, T> = ResultType<T, E> & { __IsOk__: false };
export type Ok<T, E> = ResultType<T, E> & { [IsOk]: true };
export type Err<E, T> = ResultType<T, E> & { [IsOk]: false };

class ResultType<T, E> {
private val: T | E;
readonly __IsOk__: boolean;
readonly [IsOk]: boolean;

constructor(val: T | E, ok: boolean) {
this.val = val;
this.__IsOk__ = ok;
this[IsOk] = ok;
Object.freeze(this);
}

Expand All @@ -27,7 +29,7 @@ class ResultType<T, E> {
* assert.equal(o.is(e), false);
*/
is(cmp: unknown): cmp is Result<unknown, unknown> {
return cmp instanceof ResultType && this.__IsOk__ === cmp.__IsOk__;
return cmp instanceof ResultType && this[IsOk] === cmp[IsOk];
}

/**
Expand All @@ -46,7 +48,7 @@ class ResultType<T, E> {
* assert.equal(o.eq(e), false);
*/
eq(cmp: Result<T, E>): boolean {
return this.__IsOk__ === cmp.__IsOk__ && this.val === cmp.val;
return this[IsOk] === cmp[IsOk] && this.val === cmp.val;
}

/**
Expand All @@ -65,7 +67,7 @@ class ResultType<T, E> {
* assert.equal(o.neq(e), true);
*/
neq(cmp: Result<T, E>): boolean {
return this.__IsOk__ !== cmp.__IsOk__ || this.val !== cmp.val;
return this[IsOk] !== cmp[IsOk] || this.val !== cmp.val;
}

/**
Expand All @@ -80,7 +82,7 @@ class ResultType<T, E> {
* assert.equal(x.is_ok(), false);
*/
is_ok(): this is Ok<T, E> {
return this.__IsOk__;
return this[IsOk];
}

/**
Expand All @@ -95,7 +97,7 @@ class ResultType<T, E> {
* assert.equal(x.is_err(), true);
*/
is_err(): this is Err<E, T> {
return !this.__IsOk__;
return !this[IsOk];
}

/**
Expand All @@ -112,7 +114,7 @@ class ResultType<T, E> {
const y = x.expect("Was Err"); // throws
*/
expect(msg: string): T {
if (this.__IsOk__) {
if (this[IsOk]) {
return this.val as T;
} else {
throw new Error(msg);
Expand All @@ -132,7 +134,7 @@ class ResultType<T, E> {
assert.equal(x.expect_err("Was Ok"), 1);
*/
expect_err(msg: string): E {
if (this.__IsOk__) {
if (this[IsOk]) {
throw new Error(msg);
} else {
return this.val as E;
Expand Down Expand Up @@ -188,7 +190,7 @@ class ResultType<T, E> {
* assert.equal(x.unwrap_or(1), 1);
*/
unwrap_or(def: T): T {
return this.__IsOk__ ? (this.val as T) : def;
return this[IsOk] ? (this.val as T) : def;
}

/**
Expand All @@ -202,7 +204,7 @@ class ResultType<T, E> {
* assert.equal(x.unwrap_or_else(() => 1 + 1), 2);
*/
unwrap_or_else(f: () => T): T {
return this.__IsOk__ ? (this.val as T) : f();
return this[IsOk] ? (this.val as T) : f();
}

/**
Expand Down Expand Up @@ -238,7 +240,7 @@ class ResultType<T, E> {
* assert.equal(xor.unwrap(), 1);
*/
or(resb: Result<T, E>): Result<T, E> {
return this.__IsOk__ ? this : resb;
return this[IsOk] ? this : resb;
}

/**
Expand All @@ -259,9 +261,7 @@ class ResultType<T, E> {
* assert.equal(xor.unwrap_err(), "val 10");
*/
or_else<F>(f: (err: E) => Result<T, F>): Result<T, F> {
return this.__IsOk__
? (this as unknown as Result<T, F>)
: f(this.val as E);
return this[IsOk] ? (this as unknown as Result<T, F>) : f(this.val as E);
}

/**
Expand All @@ -281,7 +281,7 @@ class ResultType<T, E> {
* assert.equal(xand.unwrap_err(), 1);
*/
and<U>(resb: Result<U, E>): Result<U, E> {
return this.__IsOk__ ? resb : (this as Err<E, any>);
return this[IsOk] ? resb : (this as Err<E, any>);
}

/**
Expand All @@ -302,7 +302,7 @@ class ResultType<T, E> {
* assert.equal(xand.unwrap_err(), 1);
*/
and_then<U>(f: (val: T) => Result<U, E>): Result<U, E> {
return this.__IsOk__ ? f(this.val as T) : (this as Err<E, any>);
return this[IsOk] ? f(this.val as T) : (this as Err<E, any>);
}

/**
Expand All @@ -316,8 +316,8 @@ class ResultType<T, E> {
*/
map<U>(f: (val: T) => U): Result<U, E> {
return new ResultType(
this.__IsOk__ ? f(this.val as T) : (this.val as E),
this.__IsOk__
this[IsOk] ? f(this.val as T) : (this.val as E),
this[IsOk]
);
}

Expand All @@ -332,8 +332,8 @@ class ResultType<T, E> {
*/
map_err<F>(op: (err: E) => F): Result<T, F> {
return new ResultType(
this.__IsOk__ ? (this.val as T) : op(this.val as E),
this.__IsOk__
this[IsOk] ? (this.val as T) : op(this.val as E),
this[IsOk]
);
}

Expand All @@ -354,7 +354,7 @@ class ResultType<T, E> {
* assert.equal(xmap.unwrap(), 1);
*/
map_or<U>(def: U, f: (val: T) => U): U {
return this.__IsOk__ ? f(this.val as T) : def;
return this[IsOk] ? f(this.val as T) : def;
}

/**
Expand All @@ -370,7 +370,7 @@ class ResultType<T, E> {
* assert.equal(xmap.unwrap(), 2);
*/
map_or_else<U>(def: (err: E) => U, f: (val: T) => U): U {
return this.__IsOk__ ? f(this.val as T) : def(this.val as E);
return this[IsOk] ? f(this.val as T) : def(this.val as E);
}

/**
Expand All @@ -389,7 +389,7 @@ class ResultType<T, E> {
* const y = x.unwrap(); // throws
*/
ok(): Option<T> {
return this.__IsOk__ ? Some(this.val as T) : None;
return this[IsOk] ? Some(this.val as T) : None;
}
}

Expand Down

0 comments on commit 77ed970

Please sign in to comment.