Skip to content

Commit

Permalink
Implement option without nominal types
Browse files Browse the repository at this point in the history
  • Loading branch information
danwang committed Feb 3, 2018
1 parent 064a66f commit f506a5b
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 22 deletions.
15 changes: 7 additions & 8 deletions src/__tests__/option.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
/* eslint-disable no-unused-expressions */
import {some, none, of, type Option} from '../option';
import {some, none, of, type Some, type None, type Option} from '../option';

describe('option', () => {
test('map', () => {
Expand All @@ -25,7 +25,8 @@ describe('option', () => {
});

test('flatMap', () => {
const maybeDiv2 = (n: number) => (n % 2 === 0 ? some(n / 2) : none);
const maybeDiv2 = (n: number): Option<number> =>
n % 2 === 0 ? some(n / 2) : none;
const div2 = (n: number) => n / 2;
expect(none.flatMap(maybeDiv2).equals(none)).toBe(true);
expect(
Expand Down Expand Up @@ -200,18 +201,16 @@ describe('option', () => {

// Inference with nonEmpty
if (opt.nonEmpty === true) {
(opt.value: 'a');
(opt: Some<'a'>);
} else {
// $ExpectError Cannot access value
(opt.value: mixed);
(opt: None);
}

// Inference with isEmpty
if (opt.isEmpty === true) {
// $ExpectError Cannot access value
(opt.value: mixed);
(opt: None);
} else {
(opt.value: 'a');
(opt: Some<'a'>);
}
};
});
56 changes: 42 additions & 14 deletions src/option.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
// @flow
/* eslint-disable no-use-before-define */

type OptionCommon<+A> = {
+get: () => A,
+map: <B>((A) => B) => Option<B>,
+flatMap: <B>((A) => Option<B>) => Option<B>,
+forEach: ((A) => void) => void,
+getOrElse: <B>(B) => A | B,
+getOrElseL: <B>(() => B) => A | B,
+filter: ((A) => boolean) => Option<A>,
+equals: (mixed) => boolean,
};

export type None = {
isEmpty: true,
nonEmpty: false,
} & OptionCommon<empty>;

export type Some<+A> = {
+value: A,
isEmpty: false,
nonEmpty: true,
} & OptionCommon<A>;

export type Option<+A> = Some<A> | None;

class AbstractOption<+A> {
// Abstract
+isEmpty: boolean;
Expand Down Expand Up @@ -40,7 +64,15 @@ class AbstractOption<+A> {
}
}

class Some<+A> extends AbstractOption<A> {
class _None<+A> extends AbstractOption<A> {
isEmpty: true = true;
nonEmpty: false = false;
get() {
throw new Error('Called None.get');
}
}

class _Some<+A> extends AbstractOption<A> {
+value: A;

constructor(value: A) {
Expand All @@ -55,20 +87,16 @@ class Some<+A> extends AbstractOption<A> {
}
}

class None<+A> extends AbstractOption<A> {
isEmpty: true = true;
nonEmpty: false = false;
get() {
throw new Error('Called None.get');
}
}

export const some = <A>(a: A): Some<A> => new Some(a);
export const none: Option<empty> = new None();
export const of = <A>(a: A): Option<$NonMaybeType<A>> =>
a === null || a === undefined ? none : some(a);
export const some = <A>(a: A): Some<A> => new _Some(a);
export const none: None = new _None();

export type Option<+A> = Some<A> | None<A>;
export const of = <A>(a: A): Option<$NonMaybeType<A>> => {
if (a === null || a === undefined) {
return none;
} else {
return some(a);
}
};

export default {
some,
Expand Down

0 comments on commit f506a5b

Please sign in to comment.