Skip to content

Commit

Permalink
add reduceRight to List, apply traversals in the proper order (#198)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilsoft authored Feb 6, 2018
1 parent 4ad2228 commit e48badc
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 12 deletions.
2 changes: 1 addition & 1 deletion docs/src/pages/docs/crocks/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ but what they do from type to type may vary.
| [`Equiv`][equiv] | [`empty`][equiv-empty] | [`concat`][equiv-concat], [`contramap`][equiv-contra], [`compareWith`][equiv-compare], [`valueOf`][equiv-value] |
| `Identity` | `of` | `ap`, `chain`, `concat`, `equals`, `map`, `of`, `sequence`, `traverse`, `valueOf` |
| `IO` | `of` | `ap`, `chain`, `map`, `of`, `run` |
| `List` | `empty`, `fromArray`, `of` | `ap`, `chain`, `concat`, `cons`, `empty`, `equals`, `filter`, `head`, `map`, `of`, `reduce`, `reject`, `sequence`, `tail`, `toArray`, `traverse`, `valueOf` |
| `List` | `empty`, `fromArray`, `of` | `ap`, `chain`, `concat`, `cons`, `empty`, `equals`, `filter`, `head`, `map`, `of`, `reduce`, `reduceRight`, `reject`, `sequence`, `tail`, `toArray`, `traverse`, `valueOf` |
| [`Maybe`][maybe] | [`Nothing`][maybe-nothing], [`Just`][maybe-just], [`of`][maybe-of], [`zero`][maybe-zero] | [`alt`][maybe-alt], [`ap`][maybe-ap], [`chain`][maybe-chain], [`coalesce`][maybe-coalesce], [`concat`][maybe-concat], [`equals`][maybe-equals], [`either`][maybe-either], [`map`][maybe-map], [`of`][maybe-of], [`option`][maybe-option], [`sequence`][maybe-sequence], [`traverse`][maybe-traverse], [`zero`][maybe-zero] |
| `Pair` | --- | `ap`, `bimap`, `chain`, `concat`, `equals`, `extend`, `fst`, `map`, `merge`, `of`, `snd`, `swap`, `toArray` |
| [`Pred`][pred] * | [`empty`][pred-empty] | [`concat`][pred-concat], [`contramap`][pred-contra], [`runWith`][pred-run], [`valueOf`][pred-value] |
Expand Down
2 changes: 2 additions & 0 deletions docs/src/pages/docs/functions/pointfree-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ accepted Datatype):
| `promap` | `(c -> a) -> (b -> d) -> m a b -> m c d` | `crocks/pointfree` |
| `read` | `m a b -> Pair a b` | `crocks/Writer` |
| `reduce` | `(b -> a -> b) -> b -> m a -> b` | `crocks/pointfree` |
| `reduceRight` | `(b -> a -> b) -> b -> m a -> b` | `crocks/pointfree` |
| `reject` | <code>((a -> Boolean) &#124; Pred a) -> m a -> m a</code> | `crocks/pointfree` |
| `run` | `m a -> b` | `crocks/pointfree` |
| `runWith` | `a -> m -> b` | `crocks/pointfree` |
Expand Down Expand Up @@ -117,6 +118,7 @@ accepted Datatype):
| `promap` | [`Arrow`][arrow-pro], `Star` |
| `read` | `Writer` |
| `reduce` | `Array`, `List` |
| `reduceRight` | `Array`, `List` |
| `reject` | `Array`, `List`, `Object` |
| `run` | `IO` |
| `runWith` | [`Arrow`][arrow-run], [`Endo`][endo-run], [`Pred`][pred-run], [`Reader`][reader-run], `Star`, [`State`][state-run] |
Expand Down
24 changes: 16 additions & 8 deletions src/core/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const predOrFunc = require('./predOrFunc')
const not =
fn => x => !fn(x)

const _concat =
x => m => m.concat(x)
const _prepend =
x => m => x.concat(m)

const { Nothing, Just } = require('./Maybe')

Expand All @@ -44,11 +44,11 @@ function fromArray(xs) {

function applyTraverse(x, y) {
if(isArray(x)) {
return array.ap(x, array.map(v => _concat(List.of(v)), y))
return array.ap(x, array.map(v => _prepend(List.of(v)), y))
}

return y
.map(v => _concat(List.of(v)))
.map(v => _prepend(List.of(v)))
.ap(x)
}

Expand Down Expand Up @@ -139,6 +139,14 @@ function List(x) {
return xs.reduce(fn, i)
}

function reduceRight(fn, i) {
if(!isFunction(fn)) {
throw new TypeError('List.reduceRight: Function required for first argument')
}

return xs.reduceRight(fn, i)
}

function fold() {
if(isEmpty(xs)) {
throw new TypeError('List.fold: List must contain at least one Semigroup')
Expand Down Expand Up @@ -216,7 +224,7 @@ function List(x) {
throw new TypeError('List.sequence: Applicative Function required')
}

return reduce(
return reduceRight(
runSequence,
af(List.empty())
)
Expand All @@ -227,7 +235,7 @@ function List(x) {
throw new TypeError('List.traverse: Applicative returning functions required for both arguments')
}

return reduce(
return reduceRight(
runTraverse(f),
af(List.empty())
)
Expand All @@ -236,8 +244,8 @@ function List(x) {
return {
inspect, toString: inspect, valueOf, toArray,
head, tail, cons, type, equals, concat, empty,
reduce, fold, filter, reject, map, ap, of, chain,
sequence, traverse,
reduce, reduceRight, fold, filter, reject, map,
ap, of, chain, sequence, traverse,
'@@type': _type,
constructor: List
}
Expand Down
27 changes: 27 additions & 0 deletions src/core/List.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,33 @@ test('List reduce functionality', t => {
t.end()
})

test('List reduceRight errors', t => {
const reduce = bindFunc(List([ 1, 2 ]).reduceRight)

const err = /List.reduceRight: Function required for first argument/
t.throws(reduce(undefined, 0), err, 'throws with undefined in the first argument')
t.throws(reduce(null, 0), err, 'throws with null in the first argument')
t.throws(reduce(0, 0), err, 'throws with falsey number in the first argument')
t.throws(reduce(1, 0), err, 'throws with truthy number in the first argument')
t.throws(reduce('', 0), err, 'throws with falsey string in the first argument')
t.throws(reduce('string', 0), err, 'throws with truthy string in the first argument')
t.throws(reduce(false, 0), err, 'throws with false in the first argument')
t.throws(reduce(true, 0), err, 'throws with true in the first argument')
t.throws(reduce({}, 0), err, 'throws with an object in the first argument')
t.throws(reduce([], 0), err, 'throws with an array in the first argument')

t.end()
})

test('List reduceRight functionality', t => {
const f = (y, x) => y.concat(x)
const m = List([ '1', '2', '3' ])

t.equal(m.reduceRight(f, '4'), '4321', 'reduces as expected')

t.end()
})

test('List fold errors', t => {
const f = bindFunc(x => List(x).fold())

Expand Down
6 changes: 3 additions & 3 deletions src/core/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const isSameType = require('./isSameType')
const identity = x => x

const concat =
x => m => m.concat(x)
x => m => x.concat(m)

function runTraverse(name, fn) {
return function(acc, x) {
Expand Down Expand Up @@ -56,11 +56,11 @@ function chain(f, m) {
}

function sequence(af, m) {
return m.reduce(runTraverse('sequence', identity), af([]))
return m.reduceRight(runTraverse('sequence', identity), af([]))
}

function traverse(af, fn, m) {
return m.reduce(runTraverse('traverse', fn), af([]))
return m.reduceRight(runTraverse('traverse', fn), af([]))
}

module.exports = {
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const pointfree = {
promap: require('./pointfree/promap'),
read: require('./Writer/read'),
reduce: require('./pointfree/reduce'),
reduceRight: require('./pointfree/reduceRight'),
reject: require('./pointfree/reject'),
run: require('./pointfree/run'),
runWith: require('./pointfree/runWith'),
Expand Down
2 changes: 2 additions & 0 deletions src/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ const option = require('./pointfree/option')
const promap = require('./pointfree/promap')
const read = require('./Writer/read')
const reduce = require('./pointfree/reduce')
const reduceRight = require('./pointfree/reduceRight')
const reject = require('./pointfree/reject')
const run = require('./pointfree/run')
const runWith = require('./pointfree/runWith')
Expand Down Expand Up @@ -328,6 +329,7 @@ test('entry', t => {
t.equal(crocks.promap, promap, 'provides the promap pointfree')
t.equal(crocks.read, read, 'provides the read pointfree')
t.equal(crocks.reduce, reduce, 'provides the reduce pointfree')
t.equal(crocks.reduceRight, reduceRight, 'provides the reduceRight pointfree')
t.equal(crocks.reject, reject, 'provides the reject pointfree')
t.equal(crocks.run, run, 'provides the run pointfree')
t.equal(crocks.runWith, runWith, 'provides the runWith pointfree')
Expand Down
18 changes: 18 additions & 0 deletions src/pointfree/reduceRight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/** @license ISC License (c) copyright 2017 original and current authors */
/** @author Ian Hofmann-Hicks (evil) */

const curry = require('../core/curry')
const isFunction = require('../core/isFunction')

function reduceRight(fn, init, m) {
if(!isFunction(fn)) {
throw new TypeError('reduceRight: Function required for first argument')
}
else if(!(m && isFunction(m.reduceRight))) {
throw new TypeError('reduceRight: Right Foldable required for third argument')
}

return m.reduceRight(fn, init)
}

module.exports = curry(reduceRight)
54 changes: 54 additions & 0 deletions src/pointfree/reduceRight.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const test = require('tape')
const sinon = require('sinon')
const helpers = require('../test/helpers')

const bindFunc = helpers.bindFunc

const isFunction = require('../core/isFunction')
const unit = require('../core/_unit')

const constant = x => () => x

const reduceRight = require('./reduceRight')

test('reduce pointfree', t => {
const r = bindFunc(reduceRight)
const x = 'result'
const m = { reduceRight: sinon.spy(constant(x)) }

t.ok(isFunction(reduceRight), 'is a function')

const noFunc = /reduceRight: Function required for first argument/
t.throws(r(undefined, 0, m), noFunc, 'throws if first arg is undefined')
t.throws(r(null, 0, m), noFunc, 'throws if first arg is null')
t.throws(r(0, 0, m), noFunc, 'throws if first arg is a falsey number')
t.throws(r(1, 0, m), noFunc, 'throws if first arg is a truthy number')
t.throws(r('', 0, m), noFunc, 'throws if first arg is a falsey string')
t.throws(r('string', 0, m), noFunc, 'throws if first arg is a truthy string')
t.throws(r(false, 0, m), noFunc, 'throws if first arg is false')
t.throws(r(true, 0, m), noFunc, 'throws if first arg is true')
t.throws(r([], 0, m), noFunc, 'throws if first arg is an array')
t.throws(r({}, 0, m), noFunc, 'throws if first arg is an object')

const err = /reduceRight: Right Foldable required for third argument/
t.throws(r(unit, 0, undefined), err, 'throws if second arg is undefined')
t.throws(r(unit, 0, null), err, 'throws if second arg is null')
t.throws(r(unit, 0, 0), err, 'throws if second arg is a falsey number')
t.throws(r(unit, 0, 1), err, 'throws if second arg is a truthy number')
t.throws(r(unit, 0, ''), err, 'throws if second arg is a falsey string')
t.throws(r(unit, 0, 'string'), err, 'throws if second arg is a truthy string')
t.throws(r(unit, 0, false), err, 'throws if second arg is false')
t.throws(r(unit, 0, true), err, 'throws if second arg is true')
t.throws(r(unit, 0, {}), err, 'throws if second arg is an object')

t.doesNotThrow(r(unit, 0, m), 'allows a function and Foldable')
t.doesNotThrow(r(unit, 0, []), 'allows a function and an array (Foldable)')

const f = sinon.spy()
const res = reduceRight(f, 0, m)

t.ok(m.reduceRight.calledWith(f), 'calls reduceRight on container, passing the function')
t.equal(res, x, 'returns the result of reduce')

t.end()
})

0 comments on commit e48badc

Please sign in to comment.