Skip to content

Commit

Permalink
feat(getOrElse): Makes getOrElse lazy
Browse files Browse the repository at this point in the history
BREAKING CHANGE: getOrElse is now lazy. To get the old getOrElse behavior, use getOrElseValue.

Also, the types are now much more explicit, which should lead to fewer typing misses in client code.
  • Loading branch information
kofno committed Dec 17, 2017
1 parent 8f2ce4c commit 015d481
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 126 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
},
"devDependencies": {
"@types/tape": "^4.2.29",
"semantic-release": "^8.0.3",
"tap-spec": "^4.1.1",
"tape": "^4.6.3",
"ts-node": "^3.0.2",
"ts-node": "^4.0.2",
"tslint": "^5.1.0",
"typedoc": "^0.5.10",
"typescript": "^2.2.2",
"semantic-release": "^8.0.3"
"typescript": "^2.6.2"
}
}
32 changes: 18 additions & 14 deletions src/Err.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,50 @@
import Catamorphism from "./Catamorphism";
import Result from "./Result";
import Catamorphism from './Catamorphism';
import Result from './Result';

class Err<E> extends Result<E, any> {
class Err<E, A> extends Result<E, A> {
private error: E;
constructor(theError: E) {
super();
this.error = theError;
}

public getOrElse<A>(defaultValue: A): A {
public getOrElse(fn: () => A): A {
return fn();
}

public getOrElseValue(defaultValue: A): A {
return defaultValue;
}

public map<B>(fn: (_: any) => B): Result<E, B> {
return this as Result<E, B>;
public map<B>(fn: (_: A) => B): Result<E, B> {
return new Err<E, B>(this.error);
}

public mapError<X>(fn: (err: E) => X): Result<X, any> {
public mapError<X>(fn: (err: E) => X): Result<X, A> {
return new Err(fn(this.error));
}

public andThen(fn: (_: any) => Result<E, any>): Result<E, any> {
return this as Result<E, any>;
public andThen<B>(fn: (_: A) => Result<E, B>): Result<E, B> {
return new Err<E, B>(this.error);
}

public orElse<X>(fn: (err: E) => Result<X, any>): Result<X, any> {
public orElse<X>(fn: (err: E) => Result<X, A>): Result<X, A> {
return fn(this.error);
}

public cata<B>(matcher: Catamorphism<E, any, B>): B {
public cata<B>(matcher: Catamorphism<E, A, B>): B {
return matcher.Err(this.error);
}

public ap(result: Result<E, any>): Result<E, any> {
return this as Result<E, any>;
public ap<B, C>(result: Result<E, B>): Result<E, C> {
return new Err<E, C>(this.error);
}
}

/**
* A convenience function for creating a new Err.
*/
function err<E>(e: E) { return new Err(e); }
const err = <E, A>(e: E): Result<E, A> => new Err<E, A>(e);

export default Err;
export { err };
32 changes: 18 additions & 14 deletions src/Ok.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,54 @@
import Catamorphism from "./Catamorphism";
import Result from "./Result";
import Catamorphism from './Catamorphism';
import Result from './Result';

class Ok<A> extends Result<any, A> {
class Ok<Err, A> extends Result<Err, A> {
private value: A;
constructor(theValue: A) {
super();
this.value = theValue;
}

public getOrElse(_: A): A {
public getOrElse(fn: () => A): A {
return this.value;
}

public map<B>(fn: (a: A) => B): Result<any, B> {
public getOrElseValue(_: A): A {
return this.value;
}

public map<B>(fn: (a: A) => B): Result<Err, B> {
return new Ok(fn(this.value));
}

public mapError<X>(fn: (e: any) => any): Result<any, A> {
return this as Result<any, A>;
public mapError<X>(fn: (e: Err) => X): Result<X, A> {
return new Ok<X, A>(this.value);
}

public andThen<B>(fn: (a: A) => Ok<B>): Result<any, B> {
return fn(this.value) as Result<any, B>;
public andThen<B>(fn: (a: A) => Result<Err, B>): Result<Err, B> {
return fn(this.value);
}

public orElse(fn: (_: any) => Result<any, A>): Result<any, A> {
return this as Result<any, A>;
}

public cata<B>(matcher: Catamorphism<any, A, B>): B {
public cata<B>(matcher: Catamorphism<Err, A, B>): B {
return matcher.Ok(this.value);
}

public ap<B, C>(result: Result<any, B>): Result<any, C> {
if (typeof this.value !== "function") {
public ap<B, C>(result: Result<Err, B>): Result<Err, C> {
if (typeof this.value !== 'function') {
throw new TypeError(`'ap' can only be applied to functions: ${JSON.stringify(this.value)}`);
}

return result.map(this.value) as Result<any, C>;
return result.map(this.value);
}
}

/**
* A convenience function for create a new Ok.
*/
function ok<T>(v: T) { return new Ok(v); }
const ok = <Err, T>(v: T): Result<Err, T> => new Ok(v);

export default Ok;
export { ok };
9 changes: 7 additions & 2 deletions src/Result.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import Catamorphism from "./Catamorphism";
import Catamorphism from './Catamorphism';

/**
* A Result represents a computation that may succeed or fail. Ok<T> represents
* a successful computation, while Err<E> represents a failure.
*/
abstract class Result<E, A> {
/**
* Returns the value from a successful result. For an error, returns the
* result of evaluating the fn
*/
public abstract getOrElse(fn: () => A): A;

/**
* Returns the value from a successful result. Returns the defaultValue if
* the result was a failure.
*/
public abstract getOrElse(defaultValue: A): A;
public abstract getOrElseValue(defaultValue: A): A;

/**
* Returns a new result after applying fn to the value stored in a successful
Expand Down
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Catamorphism from "./Catamorphism";
import Err, { err } from "./Err";
import Ok, { ok } from "./Ok";
import Result from "./Result";
import Catamorphism from './Catamorphism';
import Err, { err } from './Err';
import Ok, { ok } from './Ok';
import Result from './Result';

export { Catamorphism, Err, err, Ok, ok, Result };
46 changes: 23 additions & 23 deletions tests/Err.test.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
import * as test from "tape";
import { err, ok } from "./../src/index";
import * as test from 'tape';
import { err, ok } from './../src/index';

test("Err.getOrElse", t => {
const result = err("foo");
t.equal("bar", result.getOrElse("bar"));
test('Err.getOrElse', t => {
const result = err<string, string>('foo');
t.equal('bar', result.getOrElse(() => 'bar'));
t.end();
});

test("Err.map", t => {
const result = err("foo");
t.equal("bar", result.map(s => s.toUpperCase()).getOrElse("bar"));
test('Err.map', t => {
const result = err<string, string>('foo');
t.equal('bar', result.map(s => s.toUpperCase()).getOrElseValue('bar'));
t.end();
});

test("Err.andThen", t => {
const result = err("foo").andThen(v => err(v.toUpperCase()));
t.equal("bar", result.getOrElse("bar"));
test('Err.andThen', t => {
const result = err<string, string>('foo').andThen(v => err(v.toUpperCase()));
t.equal('bar', result.getOrElseValue('bar'));
t.end();
});

test("Err.orElse", t => {
const result = err("foo").orElse(e => ok(e.toUpperCase()));
t.equal("FOO", result.getOrElse(""));
test('Err.orElse', t => {
const result = err<string, string>('foo').orElse(e => ok(e.toUpperCase()));
t.equal('FOO', result.getOrElseValue(''));
t.end();
});

test("Err.cata", t => {
const result = err("foo").cata({
test('Err.cata', t => {
const result = err('foo').cata({
Err: err => err,
Ok: v => v,
});
t.equals("foo", result);
t.equals('foo', result);
t.end();
});

test("Err.mapError", t => {
err("foo").mapError(m => m.toUpperCase()).cata({
Err: err => t.equal("FOO", err),
Ok: v => t.fail("should not have passed"),
test('Err.mapError', t => {
err('foo').mapError(m => m.toUpperCase()).cata({
Err: err => t.equal('FOO', err),
Ok: v => t.fail('should not have passed'),
});
t.end();
});

test("Err.ap", t => {
test('Err.ap', t => {
const fn = (a: string) => (b: number) => ({ a, b });

err("oops!").ap(ok("hi")).ap(ok(42)).cata({
err('oops!').ap(ok('hi')).ap(ok(42)).cata({
Err: m => t.pass(`Failed as expected: ${m}`),
Ok: v => t.fail(`Should have failed: ${JSON.stringify(v)}`),
});
Expand Down
48 changes: 24 additions & 24 deletions tests/Ok.test.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,57 @@
import * as test from "tape";
import { err, ok } from "./../src/index";
import * as test from 'tape';
import { err, ok } from './../src/index';

test("Ok.getOrElse", t => {
const result = ok("foo");
t.equal("foo", result.getOrElse("bar"));
test('Ok.getOrElse', t => {
const result = ok<string, string>('foo');
t.equal('foo', result.getOrElse(() => 'bar'));
t.end();
});

test("OK.map", t => {
const result = ok("foo");
t.equal("FOO", result.map(s => s.toUpperCase()).getOrElse(""));
test('OK.map', t => {
const result = ok<string, string>('foo');
t.equal('FOO', result.map(s => s.toUpperCase()).getOrElse(() => ''));
t.end();
});

test("OK.andThen", t => {
const result = ok("foo").andThen(v => ok(v.toUpperCase()));
t.equal("FOO", result.getOrElse(""));
test('OK.andThen', t => {
const result = ok<string, string>('foo').andThen(v => ok(v.toUpperCase()));
t.equal('FOO', result.getOrElseValue(''));
t.end();
});

test("OK.orElse", t => {
const result = ok("foo").orElse(err => ok(err));
t.equal("foo", result.getOrElse(""));
test('OK.orElse', t => {
const result = ok<string, string>('foo').orElse(err => ok(err));
t.equal('foo', result.getOrElseValue(''));
t.end();
});

test("Ok.cata", t => {
const result = ok("foo").cata({
test('Ok.cata', t => {
const result = ok('foo').cata({
Err: err => err,
Ok: v => v,
});
t.equals("foo", result);
t.equals('foo', result);
t.end();
});

test("Ok.mapError", t => {
ok("foo").mapError(m => m.toUpperCase()).cata({
Err: err => t.fail("should have passed"),
Ok: v => t.pass("Worked!"),
test('Ok.mapError', t => {
ok<string, string>('foo').mapError(m => m.toUpperCase()).cata({
Err: err => t.fail('should have passed'),
Ok: v => t.pass('Worked!'),
});

t.end();
});

test("Ok.ap", t => {
test('Ok.ap', t => {
const fn = (a: string) => (b: number) => ({ a, b });

ok(fn).ap(ok("hi")).ap(ok(42)).cata({
ok(fn).ap(ok('hi')).ap(ok(42)).cata({
Err: m => t.fail(`Should have passed: ${m}`),
Ok: v => t.pass(`Worked!: ${JSON.stringify(v)}`),
});

ok(fn).ap(ok("hi")).ap(err("oops!")).cata({
ok(fn).ap(ok('hi')).ap(err('oops!')).cata({
Err: m => t.pass(`ap failed: ${m}`),
Ok: v => t.fail(`should have failed: ${v}`),
});
Expand Down
22 changes: 11 additions & 11 deletions tslint.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"interface-name": false,
"max-classes-per-file": false,
"arrow-parens": [true, "ban-single-arg-parens"]
},
"rulesDirectory": []
"defaultSeverity": "error",
"extends": [ "tslint:recommended" ],
"jsRules": {},
"rules": {
"interface-name": false,
"max-classes-per-file": false,
"arrow-parens": [ true, "ban-single-arg-parens" ],
"quotemark": [ true, "single", "avoid-escape" ],
"ordered-imports": false
},
"rulesDirectory": []
}
Loading

0 comments on commit 015d481

Please sign in to comment.