diff --git a/README.md b/README.md
index 47bfde2..c8a2dce 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@ structures:
* [Semigroup](#semigroup)
* [Monoid](#monoid)
* [Group](#group)
+* [Filterable](#filterable)
* [Functor](#functor)
* [Contravariant](#contravariant)
* [Apply](#apply)
@@ -33,7 +34,7 @@ structures:
* [Bifunctor](#bifunctor)
* [Profunctor](#profunctor)
-
+
## General
@@ -328,6 +329,32 @@ A value which has a Group must provide an `invert` method. The
1. `invert` must return a value of the same Group.
+### Filterable
+
+1. `v.filter(x => p(x) && q(x))` is equivalent to `v.filter(p).filter(q)` (distributivity)
+2. `v.filter(x => true)` is equivalent to `v` (identity)
+3. `v.filter(x => false)` is equivalent to `w.filter(x => false)`
+ if `v` and `w` are values of the same Filterable (annihilation)
+
+#### `filter` method
+
+```hs
+filter :: Filterable f => f a ~> (a -> Boolean) -> f a
+```
+
+A value which has a Filterable must provide a `filter` method. The `filter`
+method takes one argument:
+
+ v.filter(p)
+
+1. `p` must be a function.
+
+ 1. If `p` is not a function, the behaviour of `filter` is unspecified.
+ 2. `p` must return either `true` or `false`. If it returns any other value,
+ the behaviour of `filter` is unspecified.
+
+2. `filter` must return a value of the same Filterable.
+
### Functor
1. `u.map(a => a)` is equivalent to `u` (identity)
@@ -836,7 +863,7 @@ to implement certain methods then derive the remaining methods. Derivations:
function(f) {
function Id(value) {
this.value = value;
- };
+ }
Id.of = function(x) {
return new Id(x);
};
@@ -850,6 +877,25 @@ to implement certain methods then derive the remaining methods. Derivations:
}
```
+ - [`filter`][] may be derived from [`of`][], [`chain`][], and [`zero`][]:
+
+ ```js
+ function(pred) {
+ var F = this.constructor;
+ return this.chain(x => pred(x) ? F.of(x) : F.zero());
+ }
+ ```
+
+ - [`filter`][] may be derived from [`concat`][], [`of`][], [`zero`][], and
+ [`reduce`][]:
+
+ ```js
+ function(pred) {
+ var F = this.constructor;
+ return this.reduce((f, x) => pred(x) ? f.concat(F.of(x)) : f, F.zero());
+ }
+ ```
+
If a data type provides a method which *could* be derived, its behaviour must
be equivalent to that of the derivation (or derivations).
@@ -873,12 +919,14 @@ be equivalent to that of the derivation (or derivations).
[`equals`]: #equals-method
[`extend`]: #extend-method
[`extract`]: #extract-method
+[`filter`]: #filter-method
[`lte`]: #lte-method
[`map`]: #map-method
[`of`]: #of-method
[`promap`]: #promap-method
[`reduce`]: #reduce-method
[`sequence`]: #sequence-method
+[`zero`]: #zero-method
## Alternatives
diff --git a/figures/dependencies.dot b/figures/dependencies.dot
index d632f3f..f0cfed4 100644
--- a/figures/dependencies.dot
+++ b/figures/dependencies.dot
@@ -11,11 +11,12 @@ digraph {
Chain;
ChainRec;
Comonad;
+ Contravariant;
Extend;
+ Filterable;
Foldable;
Functor;
Group;
- Contravariant;
Monad;
Monoid;
Ord;
diff --git a/figures/dependencies.png b/figures/dependencies.png
index e4d814b..242a17e 100644
Binary files a/figures/dependencies.png and b/figures/dependencies.png differ
diff --git a/index.js b/index.js
index 6e0e13e..f26368e 100644
--- a/index.js
+++ b/index.js
@@ -13,6 +13,7 @@
concat: 'fantasy-land/concat',
empty: 'fantasy-land/empty',
invert: 'fantasy-land/invert',
+ filter: 'fantasy-land/filter',
map: 'fantasy-land/map',
contramap: 'fantasy-land/contramap',
ap: 'fantasy-land/ap',
diff --git a/internal/id.js b/internal/id.js
index 779a38c..8901b6f 100644
--- a/internal/id.js
+++ b/internal/id.js
@@ -24,6 +24,11 @@ Id.prototype[fl.concat] = function(b) {
// Monoid is not satisfiable since the type lacks a universal empty value
+// Filterable
+Id.prototype[fl.filter] = function(pred) {
+ return new Id(this.value[fl.filter](pred));
+};
+
// Foldable
Id.prototype[fl.reduce] = function(f, acc) {
return f(acc, this.value);
diff --git a/internal/patch.js b/internal/patch.js
index b7403a1..0d03f00 100644
--- a/internal/patch.js
+++ b/internal/patch.js
@@ -7,6 +7,9 @@ module.exports = () => {
Array.prototype[fl.equals] = function(y) {
return this.length === y.length && this.join('') === y.join('');
};
+ Array.prototype[fl.filter] = function(pred) {
+ return this.filter(x => pred(x));
+ };
Array.prototype[fl.map] = function(f) {
return this.map(x => f(x));
};
diff --git a/laws/filterable.js b/laws/filterable.js
new file mode 100644
index 0000000..e932df4
--- /dev/null
+++ b/laws/filterable.js
@@ -0,0 +1,34 @@
+'use strict';
+
+const {filter} = require('..');
+
+/**
+
+### Filterable
+
+1. `v.filter(x => p(x) && q(x))` is equivalent to `v.filter(p).filter(q)` (distributivity)
+2. `v.filter(x => true)` is equivalent to `v` (identity)
+3. `v.filter(x => false)` is equivalent to `w.filter(x => false)`
+ if `v` and `w` are values of the same Filterable (annihilation)
+
+**/
+
+const distributivity = eq => v => p => q => {
+ const a = v[filter](x => p(x) && q(x));
+ const b = v[filter](p)[filter](q);
+ return eq(a, b);
+};
+
+const identity = eq => v => {
+ const a = v[filter](x => true);
+ const b = v;
+ return eq(a, b);
+};
+
+const annihilation = eq => v => w => {
+ const a = v[filter](x => false);
+ const b = w[filter](x => false);
+ return eq(a, b);
+};
+
+module.exports = {distributivity, identity, annihilation};
diff --git a/test.js b/test.js
index 93d5fd0..1968dad 100644
--- a/test.js
+++ b/test.js
@@ -8,10 +8,12 @@ const alt = require('./laws/alt');
const alternative = require('./laws/alternative');
const applicative = require('./laws/applicative');
const apply = require('./laws/apply');
+const category = require('./laws/category');
const chain = require('./laws/chain');
const chainRec = require('./laws/chainrec');
const comonad = require('./laws/comonad');
const extend = require('./laws/extend');
+const filterable = require('./laws/filterable');
const foldable = require('./laws/foldable');
const functor = require('./laws/functor');
const group = require('./laws/group');
@@ -21,7 +23,6 @@ const ord = require('./laws/ord');
const plus = require('./laws/plus');
const semigroup = require('./laws/semigroup');
const semigroupoid = require('./laws/semigroupoid');
-const category = require('./laws/category');
const setoid = require('./laws/setoid');
const traversable = require('./laws/traversable');
@@ -82,6 +83,12 @@ exports.extend = {
associativity: test(extend.associativity(Id[fl.of])(equality)),
};
+exports.filterable = {
+ distributivity: test(x => filterable.distributivity(equality)(Id[fl.of]([0, 1, 2, 3, 4]))(x => x % 2 === 0)(x => x > 0)),
+ identity: test(x => filterable.identity(equality)(Id[fl.of]([1, 2, 3]))),
+ annihilation: test(x => filterable.annihilation(equality)(Id[fl.of]([1, 2, 3]))(Id[fl.of]([4, 5, 6]))),
+};
+
exports.foldable = {
associativity: test(foldable.associativity(Id[fl.of])(equality)),
};