Skip to content

Commit

Permalink
add Last Monoid and transformations
Browse files Browse the repository at this point in the history
  • Loading branch information
evilsoft committed Jun 26, 2017
1 parent 8e05b25 commit 71716cd
Show file tree
Hide file tree
Showing 20 changed files with 1,144 additions and 3 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ All `Monoids` provide `empty` and `type` function on their Constructors as well
| `Any` | Boolean | Logical OR | `false` |
| `Assign` | Object | `Object.assign` | `{}` |
| `Endo` | Function | `compose` | `identity` |
| `Last` | Maybe | Last `Just` | `Nothing` |
| `Max` | Number | `Math.max` | `-Infinity` |
| `Min` | Number | `Math.min` | `Infinity` |
| `Prod` | Number | Multiplication | `1` |
Expand Down Expand Up @@ -860,12 +861,19 @@ bad
|---|---|---|
| `arrayToList` | `[ a ] -> List a` | `(a -> [ b ]) -> a -> List b` |
| `eitherToAsync` | `Either e a -> Async e a` | `(a -> Either e b) -> a -> Async e b` |
| `eitherToLast` | `Either b a -> Last a` | `(a -> Either c b) -> a -> Last b` |
| `eitherToMaybe` | `Either b a -> Maybe a` | `(a -> Either c b) -> a -> Maybe b` |
| `eitherToResult` | `Either e a -> Result e a` | `(a -> Either e b) -> a -> Result e b` |
| `lastToAsync` | `e -> Last a -> Async e a` | `e -> (a -> Last b) -> a -> Last e b` |
| `lastToEither` | `c -> Last a -> Either c a` | `c -> (a -> Last b) -> a -> Either c b` |
| `lastToMaybe` | `Last a -> Maybe a` | `(a -> Last b) -> a -> Maybe b` |
| `lastToResult` | `c -> Last a -> Result c a` | `c -> (a -> Last b) -> a -> Result c b` |
| `listToArray` | `List a -> [ a ]` | `(a -> List b) -> a -> [ b ]` |
| `maybeToAsync` | `e -> Maybe a -> Async e a` | `e -> (a -> Maybe b) -> a -> Async e b` |
| `maybeToEither` | `c -> Maybe a -> Either c a` | `c -> (a -> Maybe b) -> a -> Either c b` |
| `maybeToLast` | `Maybe a -> Last a` | `(a -> Maybe b) -> a -> Last b` |
| `maybeToResult` | `c -> Maybe a -> Result c a` | `c -> (a -> Maybe b) -> a -> Result c b` |
| `resultToAsync` | `Result e a -> Async e a` | `(a -> Result e b) -> a -> Async e b` |
| `resultToEither` | `Result e a -> Either e a` | `(a -> Result e b) -> a -> Either e b` |
| `resultToMaybe` | `Result b a -> Maybe a` | `(a -> Result c b) -> a -> Maybe b` |
| `resultToLast` | `Result e a -> Last a` | `(a -> Result e b) -> a -> Last b` |
| `resultToMaybe` | `Result e a -> Maybe a` | `(a -> Result e b) -> a -> Maybe b` |
8 changes: 8 additions & 0 deletions crocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const monoids = {
Any: require('./monoids/Any'),
Assign: require('./monoids/Assign'),
Endo: require('./monoids/Endo'),
Last: require('./monoids/Last'),
Min: require('./monoids/Min'),
Max: require('./monoids/Max'),
Prod: require('./monoids/Prod'),
Expand Down Expand Up @@ -168,14 +169,21 @@ const predicates = {
const transforms = {
arrayToList: require('./transforms/arrayToList'),
eitherToAsync: require('./transforms/eitherToAsync'),
eitherToLast: require('./transforms/eitherToLast'),
eitherToMaybe: require('./transforms/eitherToMaybe'),
eitherToResult: require('./transforms/eitherToResult'),
lastToAsync: require('./transforms/lastToAsync'),
lastToEither: require('./transforms/lastToEither'),
lastToMaybe: require('./transforms/lastToMaybe'),
lastToResult: require('./transforms/lastToResult'),
listToArray: require('./transforms/listToArray'),
maybeToAsync: require('./transforms/maybeToAsync'),
maybeToEither: require('./transforms/maybeToEither'),
maybeToLast: require('./transforms/maybeToLast'),
maybeToResult: require('./transforms/maybeToResult'),
resultToAsync: require('./transforms/resultToAsync'),
resultToEither: require('./transforms/resultToEither'),
resultToLast: require('./transforms/resultToLast'),
resultToMaybe: require('./transforms/resultToMaybe')
}

Expand Down
16 changes: 16 additions & 0 deletions crocks.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ const All = require('./monoids/All')
const Any = require('./monoids/Any')
const Assign = require('./monoids/Assign')
const Endo = require('./monoids/Endo')
const Last = require('./monoids/Last')
const Min = require('./monoids/Min')
const Max = require('./monoids/Max')
const Prod = require('./monoids/Prod')
Expand Down Expand Up @@ -155,14 +156,21 @@ const isTraversable = require('./predicates/isTraversable')

const arrayToList = require('./transforms/arrayToList')
const eitherToAsync = require('./transforms/eitherToAsync')
const eitherToLast = require('./transforms/eitherToLast')
const eitherToMaybe = require('./transforms/eitherToMaybe')
const eitherToResult = require('./transforms/eitherToResult')
const lastToAsync = require('./transforms/lastToAsync')
const lastToEither = require('./transforms/lastToEither')
const lastToMaybe = require('./transforms/lastToMaybe')
const lastToResult = require('./transforms/lastToResult')
const listToArray = require('./transforms/listToArray')
const maybeToAsync = require('./transforms/maybeToAsync')
const maybeToEither = require('./transforms/maybeToEither')
const maybeToLast = require('./transforms/maybeToLast')
const maybeToResult = require('./transforms/maybeToResult')
const resultToAsync = require('./transforms/resultToAsync')
const resultToEither = require('./transforms/resultToEither')
const resultToLast = require('./transforms/resultToLast')
const resultToMaybe = require('./transforms/resultToMaybe')

test('entry', t => {
Expand Down Expand Up @@ -274,6 +282,7 @@ test('entry', t => {
t.equal(crocks.Any, Any, 'provides the Any monoid')
t.equal(crocks.Assign, Assign, 'provides the Assign monoid')
t.equal(crocks.Endo, Endo, 'provides the Endo monoid')
t.equal(crocks.Last, Last, 'provides the Last monoid')
t.equal(crocks.Min, Min, 'provides the Min monoid')
t.equal(crocks.Max, Max, 'provides the Max monoid')
t.equal(crocks.Prod, Prod, 'provides the Prod monoid')
Expand Down Expand Up @@ -322,13 +331,20 @@ test('entry', t => {
t.equal(crocks.arrayToList, arrayToList, 'provides the arrayToList function')
t.equal(crocks.eitherToAsync, eitherToAsync, 'provides the eitherToAsync function')
t.equal(crocks.eitherToMaybe, eitherToMaybe, 'provides the eitherToMaybe function')
t.equal(crocks.eitherToLast, eitherToLast, 'provides the eitherToLast function')
t.equal(crocks.eitherToResult, eitherToResult, 'provides the eitherToResult function')
t.equal(crocks.lastToAsync, lastToAsync, 'provides the lastToAsync function')
t.equal(crocks.lastToEither, lastToEither, 'provides the lastToEither function')
t.equal(crocks.lastToMaybe, lastToMaybe, 'provides the lastToMaybe function')
t.equal(crocks.lastToResult, lastToResult, 'provides the lastToResult function')
t.equal(crocks.listToArray, listToArray, 'provides the listToArray function')
t.equal(crocks.maybeToAsync, maybeToAsync, 'provides the maybeToAsync function')
t.equal(crocks.maybeToEither, maybeToEither, 'provides the maybeToEither function')
t.equal(crocks.maybeToLast, maybeToLast, 'provides the maybeToLast function')
t.equal(crocks.maybeToResult, maybeToResult, 'provides the maybeToResult function')
t.equal(crocks.resultToAsync, resultToAsync, 'provides the resultToAsync function')
t.equal(crocks.resultToEither, resultToEither, 'provides the resultToEither function')
t.equal(crocks.resultToLast, resultToLast, 'provides the resultToLast function')
t.equal(crocks.resultToMaybe, resultToMaybe, 'provides the resultToMaybe function')

t.end()
Expand Down
73 changes: 73 additions & 0 deletions monoids/Last.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/** @license ISC License (c) copyright 2017 original and current authors */
/** @author Ian Hofmann-Hicks (evil) */

const _implements = require('../internal/implements')
const _inspect = require('../internal/inspect')

const constant = require('../combinators/constant')
const identity = require('../combinators/identity')
const isSameType = require('../predicates/isSameType')

const Maybe = require('../crocks/Maybe')

const _empty =
() => Last(Maybe.Nothing())

const _type =
constant('Last')

function Last(x) {
if(!arguments.length) {
throw new TypeError('Last: Requires one argument')
}

const maybe =
!isSameType(Maybe, x) ? Maybe.of(x) : x.map(identity)

const value =
constant(maybe)

const type =
_type

const empty =
_empty

const inspect =
constant(`Last(${_inspect(maybe)} )`)

const option =
maybe.option

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

const n =
m.value().map(identity)

return Last(
maybe.either(
constant(n),
constant(n.either(constant(maybe), constant(n)))
)
)
}

return {
concat, empty, inspect, option, type, value
}
}

Last['@@implements'] = _implements(
[ 'concat', 'empty' ]
)

Last.empty =
_empty

Last.type =
_type

module.exports = Last
153 changes: 153 additions & 0 deletions monoids/Last.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
const test = require('tape')
const helpers = require('../test/helpers')

const bindFunc = helpers.bindFunc

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

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

const Maybe = require('../crocks/Maybe')
const Last = require('./Last')

const extract = m => m.option('empty')

test('Last', t => {
t.ok(isFunction(Last), 'is a function')
t.ok(isObject(Last(0)), 'returns an object')

t.ok(isFunction(Last.empty), 'provides an empty function')
t.ok(isFunction(Last.type), 'provides a type function')

const err = /Last: Requires one argument/
t.throws(Last, err, 'throws when passed nothing')

t.end()
})

test('Last @@implements', t => {
const f = Last['@@implements']

t.equal(f('concat'), true, 'implements concat func')
t.equal(f('empty'), true, 'implements empty func')

t.end()
})

test('Last inspect', t => {
const val = Last(0)
const just = Last(Maybe.Just(1))
const nothing = Last(Maybe.Nothing())

t.ok(isFunction(val.inspect), 'provides an inspect function')
t.equal(val.inspect(), 'Last( Just 0 )', 'returns inspect string for value construction')
t.equal(just.inspect(), 'Last( Just 1 )', 'returns inspect string for value construction')
t.equal(nothing.inspect(), 'Last( Nothing )', 'returns inspect string for value construction')

t.end()
})

test('Last value', t => {
t.ok(isFunction(Last(0).value), 'is a function')

const val = Last(1).value()
const just = Last(Maybe.Just(2)).value()
const nothing = Last(Maybe.Nothing()).value()

t.ok(isSameType(Maybe, val), 'returns a Maybe when constructed with a value')
t.ok(isSameType(Maybe, just), 'returns a Maybe when constructed with a Just')
t.ok(isSameType(Maybe, nothing), 'returns a Maybe when constructed with a nothing')

t.equal(extract(val), 1, 'returns a Just when constructed with a value')
t.equal(extract(just), 2, 'returns a Just when constructed with a Just')
t.equal(extract(nothing), 'empty', 'returns a Nothing when constructed with a Nothing')

t.end()
})

test('Last type', t => {
t.ok(isFunction(Last(0).type), 'is a function')
t.equal(Last.type, Last(0).type, 'is the same function as the static type')
t.equal(Last(0).type(), 'Last', 'reports Last')

t.end()
})

test('Last option', t => {
const val = Last('val')
const just = Last('just')
const nothing = Last('nothing')

t.equal(val.option('nothing'), 'val', 'extracts the underlying Just value')
t.equal(just.option('nothing'), 'just', 'extracts the underlying Just value')
t.equal(nothing.option('nothing'), 'nothing', 'returns the provided value with underlying Nothing')

t.end()
})

test('Last concat functionality', t => {
const a = Last('a')
const b = Last('b')

const notLast = { type: constant('Last...Not') }

const cat = bindFunc(a.concat)

const err = /Last.concat: Last required/
t.throws(cat(undefined), err, 'throws with undefined')
t.throws(cat(null), err, 'throws with null')
t.throws(cat(0), err, 'throws with falsey number')
t.throws(cat(1), err, 'throws with truthy number')
t.throws(cat(''), err, 'throws with falsey string')
t.throws(cat('string'), err, 'throws with truthy string')
t.throws(cat(false), err, 'throws with false')
t.throws(cat(true), err, 'throws with true')
t.throws(cat([]), err, 'throws with an array')
t.throws(cat({}), err, 'throws with an object')
t.throws(cat(notLast), err, 'throws when passed non-Last')

t.equal(extract(a.concat(b)), 'b', 'returns the last value concatted')

t.end()
})

test('Last concat properties (Semigroup)', t => {
const a = Last(0)
const b = Last(1)
const c = Last('')

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

t.ok(isFunction(a.concat), 'provides a concat function')
t.equal(extract(left), extract(right), 'associativity')
t.equal(a.concat(b).type(), a.type(), 'returns an Last')

t.end()
})

test('Last empty functionality', t => {
const x = Last.empty()

t.equal(x.type(), 'Last', 'provides an Last')
t.equal(extract(x), 'empty', 'provides a true value')

t.end()
})

test('Last empty properties (Monoid)', t => {
const m = Last(3)

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

const right = m.concat(m.empty())
const left = m.empty().concat(m)

t.equal(extract(right), extract(m), 'right identity')
t.equal(extract(left), extract(m), 'left identity')

t.end()
})
37 changes: 37 additions & 0 deletions transforms/eitherToLast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/** @license ISC License (c) copyright 2017 original and current authors */
/** @author Ian Hofmann-Hicks (evil) */

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

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

const Either = require('../crocks/Either')
const Last = require('../monoids/Last')

const applyTransform = either =>
either.either(Last.empty, Last)

// eitherToLast : Either b a -> Last a
// eitherToLast : (a -> Either c b) -> a -> Last b
function eitherToLast(either) {
if(isFunction(either)) {
return function(x) {
const m = either(x)

if(!isSameType(Either, m)) {
throw new TypeError('eitherToLast: Either returing function required')
}

return applyTransform(m)
}
}

if(isSameType(Either, either)) {
return applyTransform(either)
}

throw new TypeError('eitherToLast: Either or Either returing function required')
}

module.exports = curry(eitherToLast)
Loading

0 comments on commit 71716cd

Please sign in to comment.