From 77ed9707048c68c2cc98102675423ab297880ac3 Mon Sep 17 00:00:00 2001 From: mwilson Date: Thu, 24 Feb 2022 00:54:03 +0000 Subject: [PATCH] Moved Option/Result discriminated union to symbols. Fixed a bug in match that came to light with this change. --- package.json | 2 +- src/match.ts | 7 ++++-- src/monad/option.ts | 44 ++++++++++++++++++++------------------ src/monad/result.ts | 52 ++++++++++++++++++++++----------------------- 4 files changed, 55 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 8644b42..fd6b0c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oxide.ts", - "version": "0.9.3", + "version": "0.9.4", "description": "Rust's Option and Result, implemented for TypeScript.", "main": "dist", "types": "dist", diff --git a/src/match.ts b/src/match.ts index 3bee5f4..b8af930 100644 --- a/src/match.ts +++ b/src/match.ts @@ -304,8 +304,11 @@ function matches(cond: BranchCondition | 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 ( diff --git a/src/monad/option.ts b/src/monad/option.ts index 614a129..5ebab66 100644 --- a/src/monad/option.ts +++ b/src/monad/option.ts @@ -1,16 +1,18 @@ import { Result, Ok, Err } from "./result"; +const IsSome = Symbol("IsSome"); + export type Option = Some | None; -export type Some = OptionType & { __IsSome__: true }; -export type None = OptionType & { __IsSome__: false }; +export type Some = OptionType & { [IsSome]: true }; +export type None = OptionType & { [IsSome]: false }; class OptionType { 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); } @@ -28,7 +30,7 @@ class OptionType { * ``` */ is(cmp: unknown): cmp is Option { - return cmp instanceof OptionType && this.__IsSome__ === cmp.__IsSome__; + return cmp instanceof OptionType && this[IsSome] === cmp[IsSome]; } /** @@ -47,7 +49,7 @@ class OptionType { * ``` */ eq(cmp: Option): boolean { - return this.__IsSome__ === cmp.__IsSome__ && this.val === cmp.val; + return this[IsSome] === cmp[IsSome] && this.val === cmp.val; } /** @@ -66,7 +68,7 @@ class OptionType { * ``` */ neq(cmp: Option): boolean { - return this.__IsSome__ !== cmp.__IsSome__ || this.val !== cmp.val; + return this[IsSome] !== cmp[IsSome] || this.val !== cmp.val; } /** @@ -82,7 +84,7 @@ class OptionType { * ``` */ is_some(): this is Some { - return this.__IsSome__; + return this[IsSome]; } /** @@ -98,7 +100,7 @@ class OptionType { * ``` */ is_none(): this is None { - return !this.__IsSome__; + return !this[IsSome]; } /** @@ -116,7 +118,7 @@ class OptionType { * ``` */ expect(msg: string): T { - if (this.__IsSome__) { + if (this[IsSome]) { return this.val; } else { throw new Error(msg); @@ -157,7 +159,7 @@ class OptionType { * ``` */ unwrap_or(def: T): T { - return this.__IsSome__ ? this.val : def; + return this[IsSome] ? this.val : def; } /** @@ -172,7 +174,7 @@ class OptionType { * ``` */ unwrap_or_else(f: () => T): T { - return this.__IsSome__ ? this.val : f(); + return this[IsSome] ? this.val : f(); } /** @@ -210,7 +212,7 @@ class OptionType { * ``` */ or(optb: Option): Option { - return this.__IsSome__ ? this : optb; + return this[IsSome] ? this : optb; } /** @@ -227,7 +229,7 @@ class OptionType { * ``` */ or_else(f: () => Option): Option { - return this.__IsSome__ ? this : f(); + return this[IsSome] ? this : f(); } /** @@ -248,7 +250,7 @@ class OptionType { * ``` */ and(optb: Option): Option { - return this.__IsSome__ ? optb : None; + return this[IsSome] ? optb : None; } /** @@ -270,7 +272,7 @@ class OptionType { * ``` */ and_then(f: (val: T) => Option): Option { - return this.__IsSome__ ? f(this.val) : None; + return this[IsSome] ? f(this.val) : None; } /** @@ -284,7 +286,7 @@ class OptionType { * ``` */ map(f: (val: T) => U): Option { - return this.__IsSome__ ? new OptionType(f(this.val), true) : None; + return this[IsSome] ? new OptionType(f(this.val), true) : None; } /** @@ -305,7 +307,7 @@ class OptionType { * ``` */ map_or(def: U, f: (val: T) => U): U { - return this.__IsSome__ ? f(this.val) : def; + return this[IsSome] ? f(this.val) : def; } /** @@ -322,7 +324,7 @@ class OptionType { * ``` */ map_or_else(def: () => U, f: (val: T) => U): U { - return this.__IsSome__ ? f(this.val) : def(); + return this[IsSome] ? f(this.val) : def(); } /** @@ -342,7 +344,7 @@ class OptionType { * ``` */ ok_or(err: E): Result { - return this.__IsSome__ ? Ok(this.val) : Err(err); + return this[IsSome] ? Ok(this.val) : Err(err); } /** @@ -362,7 +364,7 @@ class OptionType { * ``` */ ok_or_else(f: () => E): Result { - return this.__IsSome__ ? Ok(this.val) : Err(f()); + return this[IsSome] ? Ok(this.val) : Err(f()); } } diff --git a/src/monad/result.ts b/src/monad/result.ts index a74d05b..7ff82b9 100644 --- a/src/monad/result.ts +++ b/src/monad/result.ts @@ -1,16 +1,18 @@ import { Option, Some, None } from "./option"; +const IsOk = Symbol("IsOk"); + export type Result = Ok | Err; -export type Ok = ResultType & { __IsOk__: true }; -export type Err = ResultType & { __IsOk__: false }; +export type Ok = ResultType & { [IsOk]: true }; +export type Err = ResultType & { [IsOk]: false }; class ResultType { 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); } @@ -27,7 +29,7 @@ class ResultType { * assert.equal(o.is(e), false); */ is(cmp: unknown): cmp is Result { - return cmp instanceof ResultType && this.__IsOk__ === cmp.__IsOk__; + return cmp instanceof ResultType && this[IsOk] === cmp[IsOk]; } /** @@ -46,7 +48,7 @@ class ResultType { * assert.equal(o.eq(e), false); */ eq(cmp: Result): boolean { - return this.__IsOk__ === cmp.__IsOk__ && this.val === cmp.val; + return this[IsOk] === cmp[IsOk] && this.val === cmp.val; } /** @@ -65,7 +67,7 @@ class ResultType { * assert.equal(o.neq(e), true); */ neq(cmp: Result): boolean { - return this.__IsOk__ !== cmp.__IsOk__ || this.val !== cmp.val; + return this[IsOk] !== cmp[IsOk] || this.val !== cmp.val; } /** @@ -80,7 +82,7 @@ class ResultType { * assert.equal(x.is_ok(), false); */ is_ok(): this is Ok { - return this.__IsOk__; + return this[IsOk]; } /** @@ -95,7 +97,7 @@ class ResultType { * assert.equal(x.is_err(), true); */ is_err(): this is Err { - return !this.__IsOk__; + return !this[IsOk]; } /** @@ -112,7 +114,7 @@ class ResultType { 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); @@ -132,7 +134,7 @@ class ResultType { 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; @@ -188,7 +190,7 @@ class ResultType { * 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; } /** @@ -202,7 +204,7 @@ class ResultType { * 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(); } /** @@ -238,7 +240,7 @@ class ResultType { * assert.equal(xor.unwrap(), 1); */ or(resb: Result): Result { - return this.__IsOk__ ? this : resb; + return this[IsOk] ? this : resb; } /** @@ -259,9 +261,7 @@ class ResultType { * assert.equal(xor.unwrap_err(), "val 10"); */ or_else(f: (err: E) => Result): Result { - return this.__IsOk__ - ? (this as unknown as Result) - : f(this.val as E); + return this[IsOk] ? (this as unknown as Result) : f(this.val as E); } /** @@ -281,7 +281,7 @@ class ResultType { * assert.equal(xand.unwrap_err(), 1); */ and(resb: Result): Result { - return this.__IsOk__ ? resb : (this as Err); + return this[IsOk] ? resb : (this as Err); } /** @@ -302,7 +302,7 @@ class ResultType { * assert.equal(xand.unwrap_err(), 1); */ and_then(f: (val: T) => Result): Result { - return this.__IsOk__ ? f(this.val as T) : (this as Err); + return this[IsOk] ? f(this.val as T) : (this as Err); } /** @@ -316,8 +316,8 @@ class ResultType { */ map(f: (val: T) => U): Result { 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] ); } @@ -332,8 +332,8 @@ class ResultType { */ map_err(op: (err: E) => F): Result { 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] ); } @@ -354,7 +354,7 @@ class ResultType { * assert.equal(xmap.unwrap(), 1); */ map_or(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; } /** @@ -370,7 +370,7 @@ class ResultType { * assert.equal(xmap.unwrap(), 2); */ map_or_else(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); } /** @@ -389,7 +389,7 @@ class ResultType { * const y = x.unwrap(); // throws */ ok(): Option { - return this.__IsOk__ ? Some(this.val as T) : None; + return this[IsOk] ? Some(this.val as T) : None; } }