Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation #22

Merged
merged 7 commits into from
Jul 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "origamitower"
}
}
11 changes: 3 additions & 8 deletions src/data/either/core.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const assertType = require('../../helpers/assertType');
const assertType = require('folktale/helpers/assertType');
const assertFunction = require('folktale/helpers/assertFunction');
const { data, setoid, show } = require('folktale/core/adt/');
const fl = require('fantasy-land');

Expand All @@ -9,12 +10,6 @@ const Either = data('folktale:Data.Either', {

const { Left, Right } = Either;

const assertFunction = (method, transformation) => {
if (typeof transformation !== 'function') {
throw new TypeError(`${method} expects a function, but was given ${transformation}.`);
}
};

const assertEither = assertType(Either);

// -- Functor ----------------------------------------------------------
Expand Down Expand Up @@ -61,7 +56,7 @@ Right.prototype[fl.chain] = function(transformation) {

// NOTE:
// `get` is similar to Comonad's `extract`. The reason we don't implement
// Comonad here is that `get` is partial, and not defined for Nothing
// Comonad here is that `get` is partial, and not defined for Left
// values.

Left.prototype.get = function() {
Expand Down
3 changes: 2 additions & 1 deletion src/data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

module.exports = {
maybe: require('./maybe'),
either: require('./either')
either: require('./either'),
validation: require('./validation')
};

145 changes: 145 additions & 0 deletions src/data/validation/core.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
const assertType = require('folktale/helpers/assertType');
const assertFunction = require('folktale/helpers/assertFunction');
const { data, setoid, show } = require('folktale/core/adt/');
const fl = require('fantasy-land');
const constant = require('folktale/core/lambda/constant');

const Validation = data('folktale:Data.Validation', {
Failure(value) { return { value } },
Success(value) { return { value } }
}).derive(setoid, show);

const { Success, Failure } = Validation;

const assertValidation = assertType(Validation);

// -- Functor ----------------------------------------------------------
Failure.prototype[fl.map] = function(transformation) {
assertFunction('Validation.Failure#map', transformation);
return this;
};
Success.prototype[fl.map] = function(transformation) {
assertFunction('Validation.Success#map', transformation);
return Success(transformation(this.value));
};

// -- Apply ------------------------------------------------------------
Failure.prototype[fl.ap] = function(aValidation) {
assertValidation('Failure#ap', aValidation);
return Failure.hasInstance(aValidation) ? Failure(this.value.concat(aValidation.value))
: /* otherwise */ this;
};

Success.prototype[fl.ap] = function(aValidation) {
assertValidation('Success#ap', aValidation);
return Failure.hasInstance(aValidation) ? aValidation
: /* otherwise */ aValidation.map(this.value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just return this if aValidation is a Success.

};

// -- Applicative ------------------------------------------------------
Validation[fl.of] = Success;

// -- Extracting values and recovering ---------------------------------

// NOTE:
// `get` is similar to Comonad's `extract`. The reason we don't implement
// Comonad here is that `get` is partial, and not defined for Failure
// values.

Failure.prototype.get = function() {
throw new TypeError(`Can't extract the value of a Failure.

Failure does not contain a normal value - it contains an error.
You might consider switching from Validation#get to Validation#getOrElse, or some other method
that is not partial.
`);
};

Success.prototype.get = function() {
return this.value;
};


// -- Semigroup --------------------------------------------------------
Validation[fl.concat] = function(aValidation) {
assertValidation('Validation#concat', aValidation);
return this.cata({
Failure: ({ value }) => Failure.hasInstance(aValidation) ? Failure(value.concat(aValidation.value))
: /* otherwise */ this,
Success: (_) => aValidation
});
};

// -- Extracting values and recovering ---------------------------------

// NOTE:
// `get` is similar to Comonad's `extract`. The reason we don't implement
// Comonad here is that `get` is partial, and not defined for Left
// values.

Failure.prototype.getOrElse = function(default_) {
return default_;
};

Success.prototype.getOrElse = function(_default_) {
return this.value;
};

Failure.prototype.orElse = function(handler) {
return handler(this.value);
};

Success.prototype.orElse = function(_) {
return this;
};

// -- Folds and extended transformations--------------------------------

Validation.fold = function(f, g) {
return this.cata({
Failure: ({ value }) => f(value),
Success: ({ value }) => g(value)
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failure should apply f here, and Success apply g, so we get the Left = Failure, Right = Success equivalence

};

Validation.merge = function() {
return this.value;
};

Validation.swap = function() {
return this.fold(Success, Failure);
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same consideration as above.


Validation.bimap = function(f, g) {
return this.cata({
Failure: ({ value }) => Failure(f(value)),
Success: ({ value }) => Success(g(value))
});
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same consideration as above


Success.prototype.failureMap = function(transformation) {
assertFunction('Validation.Success#failureMap', transformation);
return this;
};
Failure.prototype.failureMap = function(transformation) {
assertFunction('Validation.Failure#failureMap', transformation);
return Failure(transformation(this.value));
};


// -- JSON conversions -------------------------------------------------
Failure.prototype.toJSON = function() {
return {
'#type': 'folktale:Validation.Failure',
value: this.value
};
};
Success.prototype.toJSON = function() {
return {
'#type': 'folktale:Validation.Success',
value: this.value
};
};


module.exports = Validation;
5 changes: 5 additions & 0 deletions src/data/validation/fromNullable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { Success, Failure } = require('./core');

module.exports = (a) =>
a != null ? Success(a)
:/*else*/ Failure(a);
16 changes: 16 additions & 0 deletions src/data/validation/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//----------------------------------------------------------------------
//
// This source file is part of the Folktale project.
//
// Copyright (C) 2015-2016 Quildreen Motta.
// Licensed under the MIT licence.
//
// See LICENCE for licence information.
// See CONTRIBUTORS for the list of contributors to the project.
//
//----------------------------------------------------------------------

module.exports = {
...require('./core'),
fromNullable: require('./fromNullable')
};
5 changes: 5 additions & 0 deletions src/helpers/assertFunction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = (method, transformation) => {
if (typeof transformation !== 'function') {
throw new TypeError(`${method} expects a function, but was given ${transformation}.`);
}
};
1 change: 0 additions & 1 deletion test/core.adt.es6
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ describe('Data.ADT.derive', function() {
}).derive(show)

property('Types have a string representation', function() {
debugger
return AB.toString() === 'AB';
})

Expand Down
128 changes: 128 additions & 0 deletions test/data.validation.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//----------------------------------------------------------------------
//
// This source file is part of the Folktale project.
//
// Copyright (C) 2015-2016 Quildreen Motta.
// Licensed under the MIT licence.
//
// See LICENCE for licence information.
// See CONTRIBUTORS for the list of contributors to the project.
//
//----------------------------------------------------------------------

const { property, forall} = require('jsverify');
const _ = require('../').data.validation;

describe('Data.Validation', function() {

describe('constructors', function () {
property('fromNullable#Failure', function() {
return _.fromNullable(null).equals(_.Failure(null))
});

property('fromNullable#Success', 'json', function(a) {
return _.fromNullable(a).equals(_.Success(a))
});
});

describe('Functor', function () {
property('map', 'json', 'json -> json', function(a, f) {
return _.of(f(a)).equals(_.of(a).map(f))
});

property('Failure#map', 'json', 'json -> json', function(a, f) {
return _.Failure(a).map(f).equals(_.Failure(a))
});
});

describe('Applicative', function () {

property('of', 'json', 'json', function(a, b) {
return (a === b) === (_.of(a).equals(_.of(b)))
});
property('Success#ap', 'string', 'string', 'string -> string -> string', function(a, b, f) {
return _.Success(f)
.ap(_.Success(a))
.ap(_.Success(b))
.equals(_.Success(f(a)(b)))
});
property('Success/Failure#ap', 'string', 'string','string -> string -> string', function(a, b, f) {
return _.Success(f)
.ap(_.Success(a))
.ap(_.Failure(b))
.equals(_.Failure(b))
});
property('Failure#ap', 'string', 'string','string -> string -> string', function(a, b, f) {
return _.Success(f)
.ap(_.Failure(a))
.ap(_.Failure(b))
.equals(_.Failure(a.concat(b)))
});
});

describe('Semigroup', function () {
property('Failure#concat', 'string', 'string', function(a, b) {
return _.Failure(a).concat(_.Failure(b)).equals(_.Failure(a.concat(b)))
});
property('Success#concat', 'string', 'string', function(a, b) {
return _.Success(a).concat(_.Success(b)).equals(_.Success(b))
});
property('Success/Failure#concat', 'string', 'string', function(a, b, f) {
return _.Success(a).concat(_.Failure(b)).equals(_.Failure(b))
});
})

describe('extracting/recovering', function () {
property('Failure#getOrElse', 'json', 'json', function(a, b) {
return _.Failure(b).getOrElse(a) === a
});
property('Success#getOrElse', 'json', 'json', function(a, b) {
return _.Success(b).getOrElse(a) === b
});

property('Failure#orElse', 'json', 'json', function(a, b) {
return _.Failure(b).orElse(() => a) === a
});
property('Success#orElse', 'json', 'json', function(a, b) {
return _.Success(b).orElse(() => b).equals(_.Success(b))
});
});
describe('folds', function () {
const id = (a) => a
property('Failure#fold', 'json', 'json -> json', function(a, f) {
return _.Success(a).fold(id, f) === f(a)
});
property('Success#fold', 'json', 'json -> json', function(a, f) {
return _.Failure(a).fold(f, id) === f(a)
});

property('Failure#merge', 'json', function(a) {
return _.Success(a).merge() === a
});
property('Success#merge', 'json', function(a) {
return _.Failure(a).merge() === a
});

property('Failure#swap', 'json', function(a) {
return _.Failure(a).swap().equals(_.Success(a))
});
property('Success#swap', 'json', function(a) {
return _.Success(a).swap().equals(_.Failure(a))
});

property('Success#bimap', 'json', 'json -> json', function(a, f) {
return _.Success(a).bimap(id, f).equals(_.Success(f(a)))
});
property('Failure#bimap', 'json', 'json -> json', function(a, f) {
return _.Failure(a).bimap(f, id).equals(_.Failure(f(a)))
});

property('Failure#failureMap', 'json', 'json -> json', function(a, f) {
return _.Failure(f(a)).equals(_.Failure(a).failureMap(f))
});

property('Success#failureMap', 'json', 'json -> json', function(a, f) {
return _.Success(a).failureMap(f).equals(_.Success(a))
});
});
});