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');