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

Add concat to Maybe, Either and Identity #85

Merged
merged 1 commit into from
Feb 26, 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
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,15 @@ All `Crocks` are Constructor functions of the given type, with `Writer` being an
| `Arrow` | `empty` | `both`, `concat`, `contramap`, `empty`, `first`, `map`, `promap`, `runWith`, `second`, `value` |
| `Async` | `Rejected`, `Resolved`, `all`, `fromNode`, `fromPromise`, `of` | `alt`, `ap`, `bimap`, `chain`, `coalesce`, `fork`, `map`, `of`, `swap`, `toPromise` |
| `Const` | -- | `ap`, `chain`, `concat`, `equals`, `map`, `value` |
| `Either` | `Left`, `Right`, `of`| `alt`, `ap`, `bimap`, `chain`, `coalesce`, `either`, `equals`, `map`, `of`, `sequence`, `swap`, `traverse` |
| `Identity` | `of` | `ap`, `chain`, `equals`, `map`, `of`, `sequence`, `traverse`, `value` |
| `Either` | `Left`, `Right`, `of`| `alt`, `ap`, `bimap`, `chain`, `coalesce`, `concat`, `either`, `equals`, `map`, `of`, `sequence`, `swap`, `traverse` |
| `Identity` | `of` | `ap`, `chain`, `concat`, `equals`, `map`, `of`, `sequence`, `traverse`, `value` |
| `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`, `value` |
| `Maybe` | `Nothing`, `Just`, `of`, `zero` | `alt`, `ap`, `chain`, `coalesce`, `equals`, `either`, `map`, `of`, `option`, `sequence`, `traverse`, `zero` |
| `Maybe` | `Nothing`, `Just`, `of`, `zero` | `alt`, `ap`, `chain`, `coalesce`, `concat`, `equals`, `either`, `map`, `of`, `option`, `sequence`, `traverse`, `zero` |
| `Pair` | `of` | `ap`, `bimap`, `chain`, `concat`, `equals`, `fst`, `map`, `merge`, `of`, `snd`, `swap`, `value` |
| `Pred` * | `empty` | `concat`, `contramap`, `empty`, `runWith`, `value` |
| `Reader` | `ask`, `of`| `ap`, `chain`, `map`, `of`, `runWith` |
| `Star` | -- | `both`, `contramap`, `map`, `promap`, `runWith` |
| `Star` | -- | `both`, `concat`, `contramap`, `map`, `promap`, `runWith` |
| `State` | `get`, `gets`, `modify` `of`, `put`| `ap`, `chain`, `evalWith`, `execWith`, `map`, `of`, `runWith` |
| `Unit` | `empty`, `of` | `ap`, `chain`, `concat`, `empty`, `equals`, `map`, `of`, `value` |
| `Writer`| `of` | `ap`, `chain`, `equals`, `log`, `map`, `of`, `read`, `value` |
Expand Down Expand Up @@ -308,7 +308,7 @@ These functions provide a very clean way to build out very simple functions and
| `both` | `Arrow`, `Function`, `Star` |
| `chain` | `Async`, `Const`, `Either`, `Identity`, `IO`, `List`, `Maybe`, `Pair`, `Reader`, `State`, `Unit`, `Writer` |
| `coalesce` | `Async`, `Maybe`, `Either` |
| `concat` | `All`, `Any`, `Array`, `Arrow`, `Assign`, `Const`, `List`, `Max`, `Min`, `Pair`, `Pred`, `Prod`, `Star`, `String`, `Sum`, `Unit` |
| `concat` | `All`, `Any`, `Array`, `Arrow`, `Assign`, `Const`, `Either`, `Identity`, `List`, `Max`, `Maybe`, `Min`, `Pair`, `Pred`, `Prod`, `Star`, `String`, `Sum`, `Unit` |
| `cons` | `Array`, `List` |
| `contramap` | `Arrow`, `Pred`, `Star` |
| `either` | `Either`, `Maybe` |
Expand Down
14 changes: 13 additions & 1 deletion crocks/Either.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const isSameType = require('../predicates/isSameType')

const _inspect = require('../internal/inspect')
const defineUnion = require('../internal/defineUnion')
const innerConcat = require('../internal/innerConcat')

const constant = require('../combinators/constant')
const composeB = require('../combinators/composeB')
Expand Down Expand Up @@ -75,6 +76,17 @@ function Either(u) {
}, x)
}

function concat(m) {
if(!isSameType(Either, m)) {
throw new TypeError('Either.concat: Either of Semigroup required')
}

return either(
Either.Left,
innerConcat(Either, m)
)
}

function swap(f, g) {
if(!isFunction(f) || !isFunction(g)) {
throw new TypeError('Either.swap: Requires both left and right functions')
Expand Down Expand Up @@ -178,7 +190,7 @@ function Either(u) {
}

return {
inspect, either, type,
inspect, either, type, concat,
swap, coalesce, equals, map, bimap,
alt, ap, of, chain, sequence, traverse
}
Expand Down
113 changes: 112 additions & 1 deletion crocks/Either.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ const helpers = require('../test/helpers')

const noop = helpers.noop
const bindFunc = helpers.bindFunc
const isObject = require('../predicates/isObject')

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

const composeB = require('../combinators/composeB')
const constant = require('../combinators/constant')
Expand Down Expand Up @@ -172,6 +175,114 @@ test('Either coalesce', t => {
t.end()
})

test('Either concat errors', t => {
const m = { type: () => 'Either...Not' }

const good = Either.Right([])
const bad = Either.Left([])

const f = bindFunc(Either.Right([]).concat)
const nonEitherErr = /Either.concat: Either of Semigroup required/

t.throws(f(undefined), nonEitherErr, 'throws with undefined on Right')
t.throws(f(null), nonEitherErr, 'throws with null on Right')
t.throws(f(0), nonEitherErr, 'throws with falsey number on Right')
t.throws(f(1), nonEitherErr, 'throws with truthy number on Right')
t.throws(f(''), nonEitherErr, 'throws with falsey string on Right')
t.throws(f('string'), nonEitherErr, 'throws with truthy string on Right')
t.throws(f(false), nonEitherErr, 'throws with false on Right')
t.throws(f(true), nonEitherErr, 'throws with true on Right')
t.throws(f([]), nonEitherErr, 'throws with array on Right')
t.throws(f({}), nonEitherErr, 'throws with object on Right')
t.throws(f(m), nonEitherErr, 'throws with non-Either on Right')

const g = bindFunc(Either.Left(0).concat)

t.throws(g(undefined), nonEitherErr, 'throws with undefined on Left')
t.throws(g(null), nonEitherErr, 'throws with null on Left')
t.throws(g(0), nonEitherErr, 'throws with falsey number on Left')
t.throws(g(1), nonEitherErr, 'throws with truthy number on Left')
t.throws(g(''), nonEitherErr, 'throws with falsey string on Left')
t.throws(g('string'), nonEitherErr, 'throws with truthy string on Left')
t.throws(g(false), nonEitherErr, 'throws with false on Left')
t.throws(g(true), nonEitherErr, 'throws with true on Left')
t.throws(g([]), nonEitherErr, 'throws with array on Left')
t.throws(g({}), nonEitherErr, 'throws with object on Left')
t.throws(g(m), nonEitherErr, 'throws with non-Either on Left')

const innerErr = /Either.concat: Both containers must contain Semigroups of the same type/
const notSemiLeft = bindFunc(x => Either.Right(x).concat(good))

t.throws(notSemiLeft(undefined), innerErr, 'throws with undefined on left')
t.throws(notSemiLeft(null), innerErr, 'throws with null on left')
t.throws(notSemiLeft(0), innerErr, 'throws with falsey number on left')
t.throws(notSemiLeft(1), innerErr, 'throws with truthy number on left')
t.throws(notSemiLeft(''), innerErr, 'throws with falsey string on left')
t.throws(notSemiLeft('string'), innerErr, 'throws with truthy string on left')
t.throws(notSemiLeft(false), innerErr, 'throws with false on left')
t.throws(notSemiLeft(true), innerErr, 'throws with true on left')
t.throws(notSemiLeft({}), innerErr, 'throws with object on left')

const notSemiRight = bindFunc(x => good.concat(Either.Right(x)))

t.throws(notSemiRight(undefined), innerErr, 'throws with undefined on right')
t.throws(notSemiRight(null), innerErr, 'throws with null on right')
t.throws(notSemiRight(0), innerErr, 'throws with falsey number on right')
t.throws(notSemiRight(1), innerErr, 'throws with truthy number on right')
t.throws(notSemiRight(''), innerErr, 'throws with falsey string on right')
t.throws(notSemiRight('string'), innerErr, 'throws with truthy string on right')
t.throws(notSemiRight(false), innerErr, 'throws with false on right')
t.throws(notSemiRight(true), innerErr, 'throws with true on right')
t.throws(notSemiRight({}), innerErr, 'throws with object on right')

const noMatch = bindFunc(() => good.concat(Either.Right('')))
t.throws(noMatch({}), innerErr, 'throws with different semigroups')

t.end()
})

test('Either concat functionality', t => {
const extract =
either(identity, identity)

const left = Either.Left('Left')
const a = Either.Right([ 1, 2 ])
const b = Either.Right([ 4, 3 ])

const right = a.concat(b)
const leftRight = a.concat(left)
const leftLeft = left.concat(a)

t.ok(isSameType(Either, right), 'returns another Either with Right')
t.ok(isSameType(Either, leftRight), 'returns another Either with Left on right side')
t.ok(isSameType(Either, leftLeft), 'returns another Either with Left on left side')

t.same(extract(right), [ 1, 2, 4, 3 ], 'concats the inner semigroup with Rights')
t.equals(extract(leftRight), 'Left', 'returns a Left with a Left on right side')
t.equals(extract(leftLeft), 'Left', 'returns a Left with a Left on left side')

t.end()
})

test('Either concat properties (Semigoup)', t => {
const extract =
either(identity, identity)

const a = Either.Right([ 'a' ])
const b = Either.Right([ 'b' ])
const c = Either.Right([ 'c' ])

const left = a.concat(b).concat(c)
const right = a.concat(b.concat(c))

t.ok(isFunction(a.concat), 'provides a concat function')

t.same(extract(left), extract(right), 'associativity')
t.ok(isArray(extract(a.concat(b))), 'returns an Array')

t.end()
})

test('Either equals functionality', t => {
const la = Either.Left(0)
const lb = Either.Left(0)
Expand Down
13 changes: 11 additions & 2 deletions crocks/Identity.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const isFunction = require('../predicates/isFunction')
const isSameType = require('../predicates/isSameType')

const _inspect = require('../internal/inspect')
const innerConcat = require('../internal/innerConcat')

const composeB = require('../combinators/composeB')
const constant = require('../combinators/constant')
Expand Down Expand Up @@ -37,6 +38,14 @@ function Identity(x) {
const inspect =
constant(`Identity${_inspect(x)}`)

function concat(m) {
if(!isSameType(Identity, m)) {
throw new TypeError('Identity.concat: Identity of Semigroup required')
}

return innerConcat(Identity, m, x)
}

function map(fn) {
if(!isFunction(fn)) {
throw new TypeError('Identity.map: Function required')
Expand Down Expand Up @@ -97,8 +106,8 @@ function Identity(x) {

return {
inspect, value, type, equals,
map, ap, of, chain, sequence,
traverse
concat, map, ap, of, chain,
sequence, traverse
}
}

Expand Down
74 changes: 73 additions & 1 deletion crocks/Identity.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ const test = require('tape')
const sinon = require('sinon')
const helpers = require('../test/helpers')

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

const bindFunc = helpers.bindFunc
const noop = helpers.noop
Expand Down Expand Up @@ -88,6 +90,76 @@ test('Identity equals properties (Setoid)', t => {
t.end()
})

test('Identity concat errors', t => {
const m = { type: () => 'Identity...Not' }

const good = Identity([])

const f = bindFunc(Identity([]).concat)
const nonIdentErr = /Identity.concat: Identity of Semigroup required/

t.throws(f(undefined), nonIdentErr, 'throws with undefined')
t.throws(f(null), nonIdentErr, 'throws with null')
t.throws(f(0), nonIdentErr, 'throws with falsey number')
t.throws(f(1), nonIdentErr, 'throws with truthy number')
t.throws(f(''), nonIdentErr, 'throws with falsey string')
t.throws(f('string'), nonIdentErr, 'throws with truthy string')
t.throws(f(false), nonIdentErr, 'throws with false')
t.throws(f(true), nonIdentErr, 'throws with true')
t.throws(f([]), nonIdentErr, 'throws with array')
t.throws(f({}), nonIdentErr, 'throws with object')
t.throws(f(m), nonIdentErr, 'throws with non-Identity')

const innerErr = /Identity.concat: Both containers must contain Semigroups of the same type/
const notSemi = bindFunc(x => Identity(x).concat(good))

t.throws(notSemi(undefined), innerErr, 'throws with undefined on left')
t.throws(notSemi(null), innerErr, 'throws with null on left')
t.throws(notSemi(0), innerErr, 'throws with falsey number on left')
t.throws(notSemi(1), innerErr, 'throws with truthy number on left')
t.throws(notSemi(''), innerErr, 'throws with falsey string on left')
t.throws(notSemi('string'), innerErr, 'throws with truthy string on left')
t.throws(notSemi(false), innerErr, 'throws with false on left')
t.throws(notSemi(true), innerErr, 'throws with true on left')
t.throws(notSemi({}), innerErr, 'throws with object on left')

const noMatch = bindFunc(() => good.concat(Identity('')))
t.throws(noMatch({}), innerErr, 'throws with different semigroups')

t.end()
})

test('Identity concat functionality', t => {
const a = Identity([ 1, 2 ])
const b = Identity([ 4, 3 ])

const res = a.concat(b)

t.ok(isSameType(Identity, res), 'returns another Identity')
t.same(res.value(), [ 1, 2, 4, 3 ], 'concats the inner semigroup with Rights')

t.end()
})

test('Identity concat properties (Semigoup)', t => {
const extract =
m => m.value()

const a = Identity([ 'a' ])
const b = Identity([ 'b' ])
const c = Identity([ 'c' ])

const left = a.concat(b).concat(c)
const right = a.concat(b.concat(c))

t.ok(isFunction(a.concat), 'provides a concat function')

t.same(extract(left), extract(right), 'associativity')
t.ok(isArray(extract(a.concat(b))), 'returns an Array')

t.end()
})

test('Identity map errors', t => {
const map = bindFunc(Identity(0).map)

Expand Down
14 changes: 13 additions & 1 deletion crocks/Maybe.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const isSameType = require('../predicates/isSameType')

const _inspect = require('../internal/inspect')
const defineUnion = require('../internal/defineUnion')
const innerConcat = require('../internal/innerConcat')

const composeB = require('../combinators/composeB')
const constant = require('../combinators/constant')
Expand Down Expand Up @@ -76,6 +77,17 @@ function Maybe(u) {
}, x)
}

function concat(m) {
if(!isSameType(Maybe, m)) {
throw new TypeError('Maybe.concat: Maybe of Semigroup required')
}

return either(
Maybe.Nothing,
innerConcat(Maybe, m)
)
}

function coalesce(f, g) {
if(!isFunction(f) || !isFunction(g)) {
throw new TypeError('Maybe.coalesce: Requires both left and right functions')
Expand Down Expand Up @@ -174,7 +186,7 @@ function Maybe(u) {

return {
inspect, either, option, type,
equals, coalesce, map, alt,
concat, equals, coalesce, map, alt,
zero, ap, of, chain, sequence,
traverse
}
Expand Down
Loading