Skip to content

Commit

Permalink
Merge pull request #80 from sanctuary-js/davidchambers/fantasy-land
Browse files Browse the repository at this point in the history
  • Loading branch information
davidchambers authored Jan 8, 2018
2 parents b0c871d + 972086f commit 82250e0
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 84 deletions.
157 changes: 97 additions & 60 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
//.
/* eslint-disable max-len */
//. <pre>
//. Setoid Semigroupoid Semigroup Foldable Functor Contravariant
//. (equals) (compose) (concat) (reduce) (map) (contramap)
//. Setoid Semigroupoid Semigroup Foldable Functor Contravariant Filterable
//. (equals) (compose) (concat) (reduce) (map) (contramap) (filter)
//. | | | \ / | | | | \
//. | | | \ / | | | | \
//. | | | \ / | | | | \
Expand Down Expand Up @@ -363,6 +363,19 @@
//. ```
var Group = $('Group', [Monoid], {invert: Value});

//# Filterable :: TypeClass
//.
//. `TypeClass` value for [Filterable][].
//.
//. ```javascript
//. > Filterable.test({})
//. true
//.
//. > Filterable.test('')
//. false
//. ```
var Filterable = $('Filterable', [], {filter: Value});

//# Functor :: TypeClass
//.
//. `TypeClass` value for [Functor][].
Expand Down Expand Up @@ -766,6 +779,11 @@
return this.concat(other);
}

// Array$prototype$filter :: Array a ~> (a -> Boolean) -> Array a
function Array$prototype$filter(pred) {
return this.filter(function(x) { return pred(x); });
}

// Array$prototype$map :: Array a ~> (a -> b) -> Array b
function Array$prototype$map(f) {
return this.map(function(x) { return f(x); });
Expand Down Expand Up @@ -899,6 +917,13 @@
return result;
}

// Object$prototype$filter :: StrMap a ~> (a -> Boolean) -> StrMap a
function Object$prototype$filter(pred) {
var result = {};
forEachKey(this, function(k) { if (pred(this[k])) result[k] = this[k]; });
return result;
}

// Object$prototype$map :: StrMap a ~> (a -> b) -> StrMap b
function Object$prototype$map(f) {
var result = {};
Expand Down Expand Up @@ -1070,6 +1095,7 @@
'fantasy-land/equals': Array$prototype$equals,
'fantasy-land/lte': Array$prototype$lte,
'fantasy-land/concat': Array$prototype$concat,
'fantasy-land/filter': Array$prototype$filter,
'fantasy-land/map': Array$prototype$map,
'fantasy-land/ap': Array$prototype$ap,
'fantasy-land/chain': Array$prototype$chain,
Expand Down Expand Up @@ -1100,6 +1126,7 @@
'fantasy-land/equals': Object$prototype$equals,
'fantasy-land/lte': Object$prototype$lte,
'fantasy-land/concat': Object$prototype$concat,
'fantasy-land/filter': Object$prototype$filter,
'fantasy-land/map': Object$prototype$map,
'fantasy-land/ap': Object$prototype$ap,
'fantasy-land/alt': Object$prototype$alt,
Expand Down Expand Up @@ -1476,6 +1503,69 @@
return Group.methods.invert(group)();
}

//# filter :: Filterable f => (a -> Boolean, f a) -> f a
//.
//. Function wrapper for [`fantasy-land/filter`][]. Discards every element
//. of the given structure which does not satisfy the predicate.
//.
//. `fantasy-land/filter` implementations are provided for the following
//. built-in types: Array and Object.
//.
//. See also [`reject`](#reject).
//.
//. ```javascript
//. > filter(x => x % 2 == 1, [1, 2, 3])
//. [1, 3]
//.
//. > filter(x => x % 2 == 1, {x: 1, y: 2, z: 3})
//. {x: 1, z: 3}
//.
//. > filter(x => x % 2 == 1, Cons(1, Cons(2, Cons(3, Nil))))
//. Cons(1, Cons(3, Nil))
//.
//. > filter(x => x % 2 == 1, Nothing)
//. Nothing
//.
//. > filter(x => x % 2 == 1, Just(0))
//. Nothing
//.
//. > filter(x => x % 2 == 1, Just(1))
//. Just(1)
//. ```
function filter(pred, filterable) {
return Filterable.methods.filter(filterable)(pred);
}

//# reject :: Filterable f => (a -> Boolean, f a) -> f a
//.
//. Discards every element of the given structure which satisfies the
//. predicate.
//.
//. This function is derived from [`filter`](#filter).
//.
//. ```javascript
//. > reject(x => x % 2 == 1, [1, 2, 3])
//. [2]
//.
//. > reject(x => x % 2 == 1, {x: 1, y: 2, z: 3})
//. {y: 2}
//.
//. > reject(x => x % 2 == 1, Cons(1, Cons(2, Cons(3, Nil))))
//. Cons(2, Nil)
//.
//. > reject(x => x % 2 == 1, Nothing)
//. Nothing
//.
//. > reject(x => x % 2 == 1, Just(0))
//. Just(0)
//.
//. > reject(x => x % 2 == 1, Just(1))
//. Nothing
//. ```
function reject(pred, filterable) {
return filter(function(x) { return !pred(x); }, filterable);
}

//# map :: Functor f => (a -> b, f a) -> f b
//.
//. Function wrapper for [`fantasy-land/map`][].
Expand Down Expand Up @@ -1760,62 +1850,6 @@
return ChainRec.methods.chainRec(typeRep)(f, x);
}

//# filter :: (Applicative f, Foldable f, Monoid (f a)) => (a -> Boolean, f a) -> f a
//.
//. Filters its second argument in accordance with the given predicate.
//.
//. This function is derived from [`concat`](#concat), [`empty`](#empty),
//. [`of`](#of), and [`reduce`](#reduce).
//.
//. See also [`filterM`](#filterM).
//.
//. ```javascript
//. > filter(x => x % 2 == 1, [1, 2, 3])
//. [1, 3]
//.
//. > filter(x => x % 2 == 1, Cons(1, Cons(2, Cons(3, Nil))))
//. Cons(1, Cons(3, Nil))
//. ```
function filter(pred, m) {
// Fast path for arrays.
if (Array.isArray(m)) return m.filter(function(x) { return pred(x); });
var M = m.constructor;
return reduce(function(m, x) { return pred(x) ? concat(m, of(M, x)) : m; },
empty(M),
m);
}

//# filterM :: (Alternative m, Monad m) => (a -> Boolean, m a) -> m a
//.
//. Filters its second argument in accordance with the given predicate.
//.
//. This function is derived from [`of`](#of), [`chain`](#chain), and
//. [`zero`](#zero).
//.
//. See also [`filter`](#filter).
//.
//. ```javascript
//. > filterM(x => x % 2 == 1, [1, 2, 3])
//. [1, 3]
//.
//. > filterM(x => x % 2 == 1, Cons(1, Cons(2, Cons(3, Nil))))
//. Cons(1, Cons(3, Nil))
//.
//. > filterM(x => x % 2 == 1, Nothing)
//. Nothing
//.
//. > filterM(x => x % 2 == 1, Just(0))
//. Nothing
//.
//. > filterM(x => x % 2 == 1, Just(1))
//. Just(1)
//. ```
function filterM(pred, m) {
var M = m.constructor;
var z = zero(M);
return chain(function(x) { return pred(x) ? of(M, x) : z; }, m);
}

//# alt :: Alt f => (f a, f a) -> f a
//.
//. Function wrapper for [`fantasy-land/alt`][].
Expand Down Expand Up @@ -2196,6 +2230,7 @@
Semigroup: Semigroup,
Monoid: Monoid,
Group: Group,
Filterable: Filterable,
Functor: Functor,
Bifunctor: Bifunctor,
Profunctor: Profunctor,
Expand Down Expand Up @@ -2225,6 +2260,8 @@
concat: concat,
empty: empty,
invert: invert,
filter: filter,
reject: reject,
map: map,
bimap: bimap,
promap: promap,
Expand All @@ -2239,8 +2276,6 @@
chain: chain,
join: join,
chainRec: chainRec,
filter: filter,
filterM: filterM,
alt: alt,
zero: zero,
reduce: reduce,
Expand Down Expand Up @@ -2273,6 +2308,7 @@
//. [Contravariant]: https://github.com/fantasyland/fantasy-land#contravariant
//. [Extend]: https://github.com/fantasyland/fantasy-land#extend
//. [FL]: https://github.com/fantasyland/fantasy-land
//. [Filterable]: https://github.com/fantasyland/fantasy-land#filterable
//. [Foldable]: https://github.com/fantasyland/fantasy-land#foldable
//. [Functor]: https://github.com/fantasyland/fantasy-land#functor
//. [Group]: https://github.com/fantasyland/fantasy-land#group
Expand All @@ -2297,6 +2333,7 @@
//. [`fantasy-land/equals`]: https://github.com/fantasyland/fantasy-land#equals-method
//. [`fantasy-land/extend`]: https://github.com/fantasyland/fantasy-land#extend-method
//. [`fantasy-land/extract`]: https://github.com/fantasyland/fantasy-land#extract-method
//. [`fantasy-land/filter`]: https://github.com/fantasyland/fantasy-land#filter-method
//. [`fantasy-land/id`]: https://github.com/fantasyland/fantasy-land#id-method
//. [`fantasy-land/invert`]: https://github.com/fantasyland/fantasy-land#invert-method
//. [`fantasy-land/lte`]: https://github.com/fantasyland/fantasy-land#lte-method
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"devDependencies": {
"doctest": "0.12.x",
"eslint": "4.9.x",
"fantasy-land": "3.4.0",
"fantasy-land": "3.5.0",
"istanbul": "0.4.x",
"mocha": "2.x.x",
"remark-cli": "3.x.x",
Expand Down
8 changes: 8 additions & 0 deletions test/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ List.prototype[FL.concat] = function(other) {
Cons(this.head, Z.concat(this.tail, other));
};

List.prototype[FL.filter] = function(pred) {
return this.tag === 'Nil' ?
Nil :
pred(this.head) ?
Cons(this.head, Z.filter(pred, this.tail)) :
Z.filter(pred, this.tail);
};

List.prototype[FL.map] = function(f) {
return this.tag === 'Nil' ?
Nil :
Expand Down
4 changes: 4 additions & 0 deletions test/Maybe.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ function Maybe$prototype$concat(other) {
/* otherwise */ Maybe.Just(Z.concat(this.value, other.value));
}

Maybe.prototype[FL.filter] = function(pred) {
return this.isJust && pred(this.value) ? this : Maybe.Nothing;
};

Maybe.prototype[FL.map] = function(f) {
return this.isJust ? Maybe.Just(f(this.value)) : Maybe.Nothing;
};
Expand Down
63 changes: 40 additions & 23 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,16 @@ test('Group', function() {
eq(Z.Group.test(Sum(0)), true);
});

test('Filterable', function() {
eq(type(Z.Filterable), 'sanctuary-type-classes/TypeClass');
eq(Z.Filterable.name, 'sanctuary-type-classes/Filterable');
eq(Z.Filterable.url, 'https://github.com/sanctuary-js/sanctuary-type-classes/tree/v' + version + '#Filterable');
eq(Z.Filterable.test(null), false);
eq(Z.Filterable.test(''), false);
eq(Z.Filterable.test([]), true);
eq(Z.Filterable.test({}), true);
});

test('Functor', function() {
eq(type(Z.Functor), 'sanctuary-type-classes/TypeClass');
eq(Z.Functor.name, 'sanctuary-type-classes/Functor');
Expand Down Expand Up @@ -853,6 +863,36 @@ test('invert', function() {
eq(Z.invert(Sum(-5)), Sum(5));
});

test('filter', function() {
eq(Z.filter.length, 2);
eq(Z.filter.name, 'filter');

eq(Z.filter(odd, []), []);
eq(Z.filter(odd, [1, 2, 3, 4, 5]), [1, 3, 5]);
eq(Z.filter(odd, {}), {});
eq(Z.filter(odd, {x: 1, y: 2, z: 3}), {x: 1, z: 3});
eq(Z.filter(odd, Nil), Nil);
eq(Z.filter(odd, Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil)))))), Cons(1, Cons(3, Cons(5, Nil))));
eq(Z.filter(odd, Nothing), Nothing);
eq(Z.filter(odd, Just(0)), Nothing);
eq(Z.filter(odd, Just(1)), Just(1));
});

test('reject', function() {
eq(Z.reject.length, 2);
eq(Z.reject.name, 'reject');

eq(Z.reject(odd, []), []);
eq(Z.reject(odd, [1, 2, 3, 4, 5]), [2, 4]);
eq(Z.reject(odd, {}), {});
eq(Z.reject(odd, {x: 1, y: 2, z: 3}), {y: 2});
eq(Z.reject(odd, Nil), Nil);
eq(Z.reject(odd, Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil)))))), Cons(2, Cons(4, Nil)));
eq(Z.reject(odd, Nothing), Nothing);
eq(Z.reject(odd, Just(0)), Just(0));
eq(Z.reject(odd, Just(1)), Nothing);
});

test('map', function() {
eq(Z.map.length, 2);
eq(Z.map.name, 'map');
Expand Down Expand Up @@ -1035,29 +1075,6 @@ test('chainRec', function() {
eq(Z.chainRec(Function, stepper, 0)({step: 2, inc: 100}), 30100);
});

test('filter', function() {
eq(Z.filter.length, 2);
eq(Z.filter.name, 'filter');

eq(Z.filter(odd, []), []);
eq(Z.filter(odd, [1, 2, 3, 4, 5]), [1, 3, 5]);
eq(Z.filter(odd, Nil), Nil);
eq(Z.filter(odd, Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil)))))), Cons(1, Cons(3, Cons(5, Nil))));
});

test('filterM', function() {
eq(Z.filterM.length, 2);
eq(Z.filterM.name, 'filterM');

eq(Z.filterM(odd, []), []);
eq(Z.filterM(odd, [1, 2, 3, 4, 5]), [1, 3, 5]);
eq(Z.filterM(odd, Nil), Nil);
eq(Z.filterM(odd, Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil)))))), Cons(1, Cons(3, Cons(5, Nil))));
eq(Z.filterM(odd, Nothing), Nothing);
eq(Z.filterM(odd, Just(0)), Nothing);
eq(Z.filterM(odd, Just(1)), Just(1));
});

test('alt', function() {
eq(Z.alt.length, 2);
eq(Z.alt.name, 'alt');
Expand Down

0 comments on commit 82250e0

Please sign in to comment.