Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow traverse and sequence to accept arrays #27

Merged
merged 1 commit into from
Jan 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import curry from 'crocks/helpers/curry'
import map from 'crocks/pointfree/map'

// you can of course do the same with require statements:
const Maybe = require('crocks/crocks/Maybe')
const All = require('crocks/monoids/All')
...
```

Expand Down Expand Up @@ -109,7 +109,7 @@ Provides a means to describe a composition between two functions. it takes two f
This is a very handy dandy function, used a lot. Pass it any value and it will give you back a function that will return that same value no matter what you pass it.

#### `flip : (a -> b -> c) -> b -> a -> c`
This little function just takes a function and returns a function that takes the first two parameters in reverse. Once can compose flip calls down the line to flip all, or some of the other parameters if there are more than two. Mix and match to your :heart:s desire.
This little function just takes a function and returns a function that takes the first two parameters in reverse. One can compose flip calls down the line to flip all, or some of the other parameters if there are more than two. Mix and match to your :heart:'s desire.

#### `identity : a -> a`
This function and `constant` are the workhorses of writing code with this library. It quite simply is just a function that when you pass it something, it returns that thing right back to you. So simple, I will leave it as an exercise to reason about why this is so powerful and important.
Expand Down Expand Up @@ -151,7 +151,7 @@ These two functions are very handy for combining an entire `List` or `Array` of
There comes a time where the values you have in a `List` or an `Array` are not in the type that is needed for the `Monoid` you want to combine with. These two functions can be used to `map` some transforming function from a given type into the type needed for the `Monoid`. In essence, this function will run each value through the function before it lifts the value into the `Monoid`, before `concat` is applied. The difference between the two is that `mconcatMap` returns the result inside the `Monoid` used to combine them. Where `mreduceMap` returns the bare value itself.

#### `not : ((a -> Boolean) | Pred) -> a -> Boolean`
When you need to negate a predicate function or a `Pred`, but want a new predicate function that does the negation, then `not` is going to get you what you need. Using `not` will allow you to stay as declarative as possible. Just pass `not` your predicate function or `Pred` and it will give you back a predicate function ready for insertion into your flow. All predicate based functions in `crocks` take either a `Pred` or predicate function, so it should be easy to swap between the two.
When you need to negate a predicate function or a `Pred`, but want a new predicate function that does the negation, then `not` is going to get you what you need. Using `not` will allow you to stay as declarative as possible. Just pass `not` your predicate function or a `Pred`, and it will give you back a predicate function ready for insertion into your flow. All predicate based functions in `crocks` take either a `Pred` or predicate function, so it should be easy to swap between the two.

#### `pipe : ((a -> b), (b -> c), ..., (y -> z)) -> a -> z`
If you find yourself not able to come to terms with doing the typical right-to-left, then `crocks` provides a means to accommodate you. This function does the same thing as `compose`, the only difference is it allows you define your flows in a left-to-right manner.
Expand Down Expand Up @@ -246,11 +246,11 @@ These functions provide a very clean way to build out very simple functions and
| `run` | `m a -> b` |
| `runWith` | `a -> m -> b` |
| `second` | `(b -> c) -> m (a, b) -> m (a, c)` |
| `sequence` | `Apply f => (b -> f b) -> m (f a) -> f (m a)` |
| `sequence` | `Applicative f => (b -> f b) -> m (f a) -> f (m a)` |
| `snd` | `m a b -> b` |
| `swap` | `m a b -> m b a` |
| `tail` | `m a -> Maybe (m a)` |
| `traverse` | `Apply f => (c -> f c) -> (a -> f b) -> m (f a) -> f (m b)` |
| `traverse` | `Applicative f => (c -> f c) -> (a -> f b) -> m (f a) -> f (m b)` |
| `value` | `m a -> a` |

##### Datatypes
Expand Down Expand Up @@ -281,9 +281,9 @@ These functions provide a very clean way to build out very simple functions and
| `run` | `IO` |
| `runWith` | `Arrow`, `Pred`, `Reader`, `Star`, `State` |
| `second` | `Arrow` |
| `sequence` | `Either`, `Identity`, `List`, `Maybe` |
| `sequence` | `Array`, `Either`, `Identity`, `List`, `Maybe` |
| `snd` | `Pair` |
| `swap` | `Pair` |
| `tail` | `Array`, `List`, `String` |
| `traverse` | `Either`, `Identity`, `List`, `Maybe` |
| `traverse` | `Array`, `Either`, `Identity`, `List`, `Maybe` |
| `value` | `Arrow`, `Const`, `Either`, `Identity`, `List`, `Pair`, `Pred`, `Unit`, `Writer` |
26 changes: 22 additions & 4 deletions pointfree/sequence.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,35 @@

const curry = require('../helpers/curry')

const isApplicative = require('../predicates/isApplicative')
const isArray = require('../predicates/isArray')
const isFunction = require('../predicates/isFunction')

const concat = require('./concat')

function runSequence(acc, x) {
if(!isApplicative(x)) {
throw new TypeError('sequence: Must wrap Applicatives')
}

return x
.map(v => concat([ v ]))
.ap(acc)
}

function sequence(af, m) {
if(!isFunction(af)) {
throw new TypeError('sequence: Applicative function required for first argument')
}
else if(!(m && isFunction(m.sequence))) {
throw new TypeError('sequence: Traversable required for second argument')
else if((m && isFunction(m.sequence))) {
return m.sequence(af)
}
else if((isArray(m))) {
return m.reduce(runSequence, af([]))
}
else {
throw new TypeError('sequence: Traversable or Array required for second argument')
}

return m.sequence(af)
}

module.exports = curry(sequence)
25 changes: 23 additions & 2 deletions pointfree/sequence.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ const helpers = require('../test/helpers')
const bindFunc = helpers.bindFunc
const noop = helpers.noop

const isArray = require('../predicates/isArray')
const isFunction = require('../predicates/isFunction')

const constant = require('../combinators/constant')

const MockCrock = require('../test/MockCrock')

const sequence = require('./sequence')

test('sequence pointfree', t => {
const seq = bindFunc(sequence)

const x = 'super cool'
const m = { sequence: sinon.spy(constant(x)) }
const m = { sequence: constant(23) }

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

Expand All @@ -41,6 +43,14 @@ test('sequence pointfree', t => {
t.throws(seq(noop, {}), 'throws if second arg is an object')

t.doesNotThrow(seq(noop, m), 'allows a function and Traverable')
t.doesNotThrow(seq(noop, []), 'allows a function and an Array')

t.end()
})

test('sequence with Traversable', t => {
const x = 'gnarly dude'
const m = { sequence: sinon.spy(constant(x)) }

const f = sinon.spy()
const res = sequence(f, m)
Expand All @@ -50,3 +60,14 @@ test('sequence pointfree', t => {

t.end()
})

test('sequence with Array', t => {
const outer = sequence(MockCrock.of, [ MockCrock(12), MockCrock(23) ])
const inner = outer.value()

t.equal(outer.type(), 'MockCrock', 'outer container is a MockCrock')
t.ok(isArray(inner), 'inner container is an Array')
t.equal((inner.length), 2, 'inner array maintains structure')

t.end()
})
40 changes: 31 additions & 9 deletions pointfree/traverse.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,42 @@

const curry = require('../helpers/curry')

const isApplicative = require('../predicates/isApplicative')
const isArray = require('../predicates/isArray')
const isFunction = require('../predicates/isFunction')

function traverse(fn, af, m) {
if(!isFunction(fn)) {
throw new TypeError('traverse: Applicative returning function required for first argument')
const concat = require('./concat')

function runTraverse(f) {
return function(acc, x) {
const m = f(x)

if(!isApplicative(acc) || !isApplicative(m)) {
throw new TypeError('traverse: Both functions must return an Applicative')
}

return m
.map(v => concat([ v ]))
.ap(acc)
}
else if(!isFunction(af)) {
throw new TypeError('traverse: Applicative function required for second argument')
}

function traverse(af, fn, m) {
if(!isFunction(af)) {
throw new TypeError('traverse: Applicative function required for first argument')
}
else if(!(m && isFunction(m.traverse))) {
throw new TypeError('traverse: Traversable required for third argument')
else if(!isFunction(fn)) {
throw new TypeError('traverse: Applicative returning function required for second argument')
}
else if((m && isFunction(m.traverse))) {
return m.traverse(af, fn)
}
else if((isArray(m))) {
return m.reduce(runTraverse(fn), af([]))
}
else {
throw new TypeError('traverse: Traversable or Array required for third argument')
}

return m.traverse(fn, af)
}

module.exports = curry(traverse)
25 changes: 24 additions & 1 deletion pointfree/traverse.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ const helpers = require('../test/helpers')
const bindFunc = helpers.bindFunc
const noop = helpers.noop

const isArray = require('../predicates/isArray')
const isFunction = require('../predicates/isFunction')

const constant = require('../combinators/constant')

const MockCrock = require('../test/MockCrock')

const traverse = require('./traverse')

test('traverse pointfree', t => {
Expand Down Expand Up @@ -49,10 +52,17 @@ test('traverse pointfree', t => {
t.throws(trav(noop, noop, 'string'), 'throws if third arg is a truthy string')
t.throws(trav(noop, noop, false), 'throws if third arg is false')
t.throws(trav(noop, noop, true), 'throws if third arg is true')
t.throws(trav(noop, noop, []), 'throws if third arg is an array')
t.throws(trav(noop, noop, {}), 'throws if third arg is an object')

t.doesNotThrow(trav(noop, noop, m), 'allows two functions and a Traverable')
t.doesNotThrow(trav(noop, noop, []), 'allows two functions and an Array ')

t.end()
})

test('traverse with Traversable', t => {
const x = 'super sweet'
const m = { traverse: sinon.spy(constant(x)) }

const f = sinon.spy()
const g = sinon.spy()
Expand All @@ -63,3 +73,16 @@ test('traverse pointfree', t => {

t.end()
})

test('traverse with Array', t => {
const f = sinon.spy(x => MockCrock(x + 2))

const outer = traverse(MockCrock.of, f, [ 12, 23 ])
const inner = outer.value()

t.equal(outer.type(), 'MockCrock', 'outer container is a MockCrock')
t.ok(isArray(inner), 'inner container is an Array')
t.same((inner), [ 14, 25 ], 'mapping/lifting function applied to every element')

t.end()
})