Skip to content

Commit

Permalink
Merge pull request #5 from alexlafroscia/use-instanceof
Browse files Browse the repository at this point in the history
Use helper functions instead of methods
  • Loading branch information
alexlafroscia authored Aug 9, 2022
2 parents 637bd8e + 97a5104 commit c198fe4
Show file tree
Hide file tree
Showing 18 changed files with 140 additions and 129 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this
project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased

### Added

- `isSome`, `isNone`, `isOk` and `isErr` helper functions
- `None` is now immutable

### Removed

- `isSome`, `isNone`, `isOk` and `isErr` methods have been removed

## 0.6.0 - 2022-08-09

### Added
Expand Down
24 changes: 24 additions & 0 deletions lib/mod.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { assertExists } from "./test-deps.ts";
import * as mod from "./mod.ts";

Deno.test("module exports", () => {
assertExists(mod.EventualResult, "Exports `EventualResult`");

assertExists(mod.Ok, "Exports `Ok`");
assertExists(mod.Err, "Exports `Err`");

assertExists(mod.isOk, "Exports `isOk`");
assertExists(mod.isErr, "Exports `isErr`");

assertExists(mod.anyResult, "Exports `anyResult`");
assertExists(mod.allResults, "Exports `allResults`");

assertExists(mod.Some, "Exports `Some`");
assertExists(mod.None, "Exports `None`");

assertExists(mod.isSome, "Exports `isSome`");
assertExists(mod.isNone, "Exports `isNone`");

assertExists(mod.UnwrapError, "Exports `UnwrapError`");
assertExists(mod.ExpectError, "Exports `ExpectError`");
});
4 changes: 2 additions & 2 deletions lib/option/mod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { type Option } from "./option.ts";
export { Some } from "./some.ts";
export { None } from "./none.ts";
export { isSome, Some } from "./some.ts";
export { isNone, None } from "./none.ts";
20 changes: 15 additions & 5 deletions lib/option/none.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { assertEquals, assertThrows } from "../test-deps.ts";
import { None } from "./none.ts";
import { isSome } from "./some.ts";
import { isNone, None } from "./none.ts";
import { ExpectError, UnwrapError } from "../exceptions.ts";
import { Err } from "../result/err.ts";

Deno.test("#isSome", () => {
assertEquals(None.isSome(), false);
Deno.test("isSome", () => {
assertEquals(isSome(None), false);
});

Deno.test("#isNone", () => {
assertEquals(None.isNone(), true);
Deno.test("isNone", () => {
assertEquals(isNone(None), true);
});

Deno.test("#unwrap", () => {
Expand Down Expand Up @@ -62,3 +63,12 @@ Deno.test("#okOr", () => {
Deno.test("#okOrElse", () => {
assertEquals(None.okOrElse(() => "error"), new Err("error"));
});

Deno.test("mutating `None`", () => {
assertThrows(() => {
// @ts-expect-error: `None` is known to be `ReadOnly` by TypeScript, but we want to ignore that
None.unwrap = () => {
throw new Error("whatever");
};
});
});
18 changes: 8 additions & 10 deletions lib/option/none.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import { type Option } from "./option.ts";
import { type Some } from "./some.ts";
import { Err, type Result } from "../result/mod.ts";
import { ExpectError, UnwrapError } from "../exceptions.ts";

class NoneImpl implements Option<never> {
isSome(): this is Some<never> {
return false;
}

isNone(): this is typeof None {
return true;
}

unwrap(): never {
throw new UnwrapError("Cannot unwrap `None`");
}
Expand Down Expand Up @@ -68,4 +59,11 @@ class NoneImpl implements Option<never> {
/**
* Represents no value in an `Option<T>`
*/
export const None = new NoneImpl();
export const None = Object.freeze(new NoneImpl());

/**
* Determines whether an `Option<T>` is `None`
*/
export function isNone(option: Option<unknown>): option is NoneImpl {
return option === None;
}
25 changes: 3 additions & 22 deletions lib/option/option.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { assert, assertEquals } from "../test-deps.ts";
import { type Option } from "./option.ts";
import { Some } from "./some.ts";
import { None } from "./none.ts";
import { isOk } from "../result/ok.ts";

function toOption<T>(value: T | undefined): Option<T> {
return typeof value !== "undefined" ? new Some(value) : None;
Expand Down Expand Up @@ -82,32 +83,12 @@ Deno.test("method signatures of `Ok` and `Err` align", async (t) => {
await t.step("#okOr", () => {
const result = toOption(1).okOr("Error!");

assert(result.isOk);
assert(isOk(result));
});

await t.step("#okOrElse", () => {
const result = toOption(1).okOrElse(() => "Error!");

assert(result.isOk);
});
});

// These tests ensure that `isSome` and `isNone` actually discriminate an
// `Option<T>` into either a `Some<T>` or a `None`. The "test" here are the
// assignments to narrower types within the `if`/`else` statement: this code
// won't compile if the intended behavior in the type system is not working
Deno.test("discriminating `Some` from `None`", async (t) => {
const result = toOption(1);

await t.step("using `#isSome`", () => {
if (result.isSome()) {
const _some: Some<number> = result;
}
});

await t.step("using `#isNone`", () => {
if (result.isNone()) {
const _none: typeof None = result;
}
assert(isOk(result));
});
});
5 changes: 0 additions & 5 deletions lib/option/option.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { type Some } from "./some.ts";
import { type None } from "./none.ts";
import { type Result } from "../result/mod.ts";

export interface Option<T> {
isSome(): this is Some<T>;
isNone(): this is typeof None;

unwrap(): T;
unwrapOr(fallback: T): T;
unwrapOrElse(fallback: () => T): T;
Expand Down
11 changes: 6 additions & 5 deletions lib/option/some.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { assertEquals } from "../test-deps.ts";
import { Some } from "./some.ts";
import { isSome, Some } from "./some.ts";
import { isNone } from "./none.ts";
import { Ok } from "../result/mod.ts";

Deno.test("#isSome", () => {
assertEquals(new Some(1).isSome(), true);
Deno.test("isSome", () => {
assertEquals(isSome(new Some(1)), true);
});

Deno.test("#isNone", () => {
assertEquals(new Some(1).isNone(), false);
Deno.test("isNone", () => {
assertEquals(isNone(new Some(1)), false);
});

Deno.test("#unwrap", () => {
Expand Down
16 changes: 7 additions & 9 deletions lib/option/some.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { type Option } from "./option.ts";
import { type None } from "./none.ts";
import { Ok, type Result } from "../result/mod.ts";

/**
Expand All @@ -8,14 +7,6 @@ import { Ok, type Result } from "../result/mod.ts";
* @template T The type of the present value
*/
export class Some<T> implements Option<T> {
isSome(): this is Some<T> {
return true;
}

isNone(): this is typeof None {
return false;
}

constructor(private val: T) {}

unwrap(): T {
Expand Down Expand Up @@ -70,3 +61,10 @@ export class Some<T> implements Option<T> {
return new Ok(this.val);
}
}

/**
* Determines whether an `Option<T>` is `Some<T>`
*/
export function isSome<T>(option: Option<T>): option is Some<T> {
return option instanceof Some;
}
12 changes: 6 additions & 6 deletions lib/result/aggregators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type Result } from "./result.ts";
import { Ok } from "./ok.ts";
import { Err } from "./err.ts";
import { isOk, Ok } from "./ok.ts";
import { Err, isErr } from "./err.ts";

/**
* Combine an iterable of `Result<T, E>` into a single `Result<T[], E>`
Expand All @@ -14,11 +14,11 @@ export function all<T, E>(results: Iterable<Result<T, E>>): Result<T[], E> {
const collection = [];

for (const result of results) {
if (result.isOk()) {
if (isOk(result)) {
collection.push(result.unwrap());
}

if (result.isErr()) {
if (isErr(result)) {
return result;
}
}
Expand All @@ -38,11 +38,11 @@ export function any<T, E>(results: Iterable<Result<T, E>>): Result<T, E[]> {
const collection = [];

for (const result of results) {
if (result.isErr()) {
if (isErr(result)) {
collection.push(result.unwrapErr());
}

if (result.isOk()) {
if (isOk(result)) {
return result;
}
}
Expand Down
11 changes: 6 additions & 5 deletions lib/result/err.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ import {
assertStrictEquals,
assertThrows,
} from "../test-deps.ts";
import { Err } from "./err.ts";
import { isOk } from "./ok.ts";
import { Err, isErr } from "./err.ts";
import { EventualResult } from "./eventual.ts";
import { None, Some } from "../option/mod.ts";
import { ExpectError, UnwrapError } from "../exceptions.ts";

Deno.test("#isOk", () => {
Deno.test("isOk", () => {
const err = new Err("whatever");

assertEquals(err.isOk(), false);
assertEquals(isOk(err), false);
});

Deno.test("#isErr", () => {
Deno.test("isErr", () => {
const err = new Err("whatever");

assertEquals(err.isErr(), true);
assertEquals(isErr(err), true);
});

Deno.test("#unwrap", () => {
Expand Down
16 changes: 7 additions & 9 deletions lib/result/err.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { type Result } from "./result.ts";
import { type Ok } from "./ok.ts";
import { EventualResult } from "./eventual.ts";
import { None, type Option, Some } from "../option/mod.ts";
import { ExpectError, UnwrapError } from "../exceptions.ts";
Expand All @@ -12,14 +11,6 @@ import { ExpectError, UnwrapError } from "../exceptions.ts";
export class Err<E> implements Result<never, E> {
constructor(private val: E) {}

isOk(): this is Ok<never> {
return false;
}

isErr(): this is Err<E> {
return true;
}

unwrap(): never {
throw new UnwrapError("Cannot unwrap `Err`", this.val);
}
Expand Down Expand Up @@ -88,3 +79,10 @@ export class Err<E> implements Result<never, E> {
return "Err";
}
}

/**
* Determines whether a `Result<T, E>` is `Err<E>`
*/
export function isErr<E>(result: Result<unknown, E>): result is Err<E> {
return result instanceof Err;
}
4 changes: 2 additions & 2 deletions lib/result/mod.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { type Result } from "./result.ts";
export { Ok } from "./ok.ts";
export { Err } from "./err.ts";
export { isOk, Ok } from "./ok.ts";
export { Err, isErr } from "./err.ts";
export { EventualResult } from "./eventual.ts";
export { all as allResults, any as anyResult } from "./aggregators.ts";
11 changes: 6 additions & 5 deletions lib/result/ok.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ import {
assertStrictEquals,
assertThrows,
} from "../test-deps.ts";
import { Ok } from "./ok.ts";
import { isOk, Ok } from "./ok.ts";
import { isErr } from "./err.ts";
import { EventualResult } from "./eventual.ts";
import { None, Some } from "../option/mod.ts";
import { ExpectError, UnwrapError } from "../exceptions.ts";

Deno.test("#isOk", () => {
Deno.test("isOk", () => {
const ok = new Ok("whatever");

assertEquals(ok.isOk(), true);
assertEquals(isOk(ok), true);
});

Deno.test("#isErr", () => {
Deno.test("isErr", () => {
const ok = new Ok("whatever");

assertEquals(ok.isErr(), false);
assertEquals(isErr(ok), false);
});

Deno.test("#unwrap", () => {
Expand Down
16 changes: 7 additions & 9 deletions lib/result/ok.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { type Result } from "./result.ts";
import { type Err } from "./err.ts";
import { EventualResult } from "./eventual.ts";
import { None, type Option, Some } from "../option/mod.ts";
import { ExpectError, UnwrapError } from "../exceptions.ts";
Expand All @@ -12,14 +11,6 @@ import { ExpectError, UnwrapError } from "../exceptions.ts";
export class Ok<T> implements Result<T, never> {
constructor(private val: T) {}

isOk(): this is Ok<T> {
return true;
}

isErr(): this is Err<never> {
return false;
}

unwrap(): T {
return this.val;
}
Expand Down Expand Up @@ -90,3 +81,10 @@ export class Ok<T> implements Result<T, never> {
return "Ok";
}
}

/**
* Determines whether a `Result<T, E>` is `Ok<T>`
*/
export function isOk<T>(result: Result<T, unknown>): result is Ok<T> {
return result instanceof Ok;
}
Loading

0 comments on commit c198fe4

Please sign in to comment.