Skip to content

Commit

Permalink
add Filterable algebra
Browse files Browse the repository at this point in the history
  • Loading branch information
davidchambers committed Jan 6, 2018
1 parent 832dc8d commit 59766a3
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 4 deletions.
52 changes: 50 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ structures:
* [Semigroup](#semigroup)
* [Monoid](#monoid)
* [Group](#group)
* [Filterable](#filterable)
* [Functor](#functor)
* [Contravariant](#contravariant)
* [Apply](#apply)
Expand All @@ -33,7 +34,7 @@ structures:
* [Bifunctor](#bifunctor)
* [Profunctor](#profunctor)

<img src="figures/dependencies.png" width="888" height="257" />
<img src="figures/dependencies.png" width="888" height="234" />

## General

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
};
Expand All @@ -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).

Expand All @@ -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

Expand Down
3 changes: 2 additions & 1 deletion figures/dependencies.dot
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ digraph {
Chain;
ChainRec;
Comonad;
Contravariant;
Extend;
Filterable;
Foldable;
Functor;
Group;
Contravariant;
Monad;
Monoid;
Ord;
Expand Down
Binary file modified figures/dependencies.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
5 changes: 5 additions & 0 deletions internal/id.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions internal/patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
};
Expand Down
34 changes: 34 additions & 0 deletions laws/filterable.js
Original file line number Diff line number Diff line change
@@ -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};
9 changes: 8 additions & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');

Expand Down Expand Up @@ -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)),
};
Expand Down

0 comments on commit 59766a3

Please sign in to comment.