diff --git a/index.js b/index.js index c18ee71..70c75b6 100644 --- a/index.js +++ b/index.js @@ -32,8 +32,8 @@ //. /* eslint-disable max-len */ //.
-//. 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) //. | | | \ / | | | | \ //. | | | \ / | | | | \ //. | | | \ / | | | | \ @@ -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][]. @@ -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); }); @@ -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 = {}; @@ -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, @@ -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, @@ -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`][]. @@ -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`][]. @@ -2196,6 +2230,7 @@ Semigroup: Semigroup, Monoid: Monoid, Group: Group, + Filterable: Filterable, Functor: Functor, Bifunctor: Bifunctor, Profunctor: Profunctor, @@ -2225,6 +2260,8 @@ concat: concat, empty: empty, invert: invert, + filter: filter, + reject: reject, map: map, bimap: bimap, promap: promap, @@ -2239,8 +2276,6 @@ chain: chain, join: join, chainRec: chainRec, - filter: filter, - filterM: filterM, alt: alt, zero: zero, reduce: reduce, @@ -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 @@ -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 diff --git a/package.json b/package.json index 9a9e93c..1594a51 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/test/List.js b/test/List.js index 55f76e3..c872c37 100644 --- a/test/List.js +++ b/test/List.js @@ -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 : diff --git a/test/Maybe.js b/test/Maybe.js index 1ad9a3f..6686993 100644 --- a/test/Maybe.js +++ b/test/Maybe.js @@ -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; }; diff --git a/test/index.js b/test/index.js index 77b5796..3e93d1b 100644 --- a/test/index.js +++ b/test/index.js @@ -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'); @@ -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'); @@ -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');