diff --git a/README.md b/README.md index 9a7b19b..a16bfff 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ structures: * [Applicative](#applicative) * [Alt](#alt) * [Plus](#plus) +* [Filterable](#filterable) * [Alternative](#alternative) * [Foldable](#foldable) * [Traversable](#traversable) @@ -33,7 +34,7 @@ structures: * [Bifunctor](#bifunctor) * [Profunctor](#profunctor) - + ## General @@ -497,6 +498,34 @@ Given a value `x`, one can access its type representative via the 1. `zero` must return a value of the same Plus +### Filterable + +A value that implements the Filterable specification must also implement +the [Plus](#plus) specification. + +1. `f.filter(x => true)` is equivalent to `f` (identity) +2. `f.filter(x => false)` is equivalent to `f.constructor.zero()` (annihilation) +3. `f.filter(p).filter(p)` is equivalent to `f.filter(p)` (idempotence) + +#### `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: + + f.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. + ### Alternative A value that implements the Alternative specification must also implement @@ -836,7 +865,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 +879,15 @@ 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()); + } + ``` + 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 +911,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..3d102ea 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; @@ -44,6 +45,7 @@ digraph { Functor -> Traversable; Monoid -> Group; Plus -> Alternative; + Plus -> Filterable; Semigroup -> Monoid; Semigroupoid -> Category; Setoid -> Ord; diff --git a/figures/dependencies.png b/figures/dependencies.png index e4d814b..a94963c 100644 Binary files a/figures/dependencies.png and b/figures/dependencies.png differ diff --git a/index.js b/index.js index 6e0e13e..a3cefd0 100644 --- a/index.js +++ b/index.js @@ -19,6 +19,7 @@ of: 'fantasy-land/of', alt: 'fantasy-land/alt', zero: 'fantasy-land/zero', + filter: 'fantasy-land/filter', reduce: 'fantasy-land/reduce', traverse: 'fantasy-land/traverse', chain: 'fantasy-land/chain', diff --git a/laws/filterable.js b/laws/filterable.js new file mode 100644 index 0000000..6f207a6 --- /dev/null +++ b/laws/filterable.js @@ -0,0 +1,33 @@ +'use strict'; + +const {filter, zero} = require('..'); + +/** + +### Filterable + +1. `f.filter(x => true)` is equivalent to `f` (identity) +2. `f.filter(x => false)` is equivalent to `f.constructor.zero()` (annihilation) +3. `f.filter(p).filter(p)` is equivalent to `f.filter(p)` (idempotence) + +**/ + +const identity = eq => filterable => { + const a = filterable[filter](x => true); + const b = filterable; + return eq(a, b); +}; + +const annihilation = eq => filterable => { + const a = filterable[filter](x => false); + const b = filterable.constructor[zero](); + return eq(a, b); +}; + +const idempotence = eq => pred => filterable => { + const a = filterable[filter](pred)[filter](pred); + const b = filterable[filter](pred); + return eq(a, b); +}; + +module.exports = {identity, annihilation, idempotence};