Skip to content

Commit

Permalink
better compose and pipe functions (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilsoft authored May 19, 2017
1 parent ffa181f commit af7e578
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 122 deletions.
30 changes: 23 additions & 7 deletions helpers/compose.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
/** @license ISC License (c) copyright 2016 original and current authors */
/** @author Ian Hofmann-Hicks (evil) */

const pipe = require('./pipe')
const argsArray = require('../internal/argsArray')

const identity = require('../combinators/identity')
const isFunction = require('../predicates/isFunction')

const err = 'compose: Functions required'

function applyPipe(f, g) {
if(!isFunction(g)) {
throw new TypeError(err)
}

return function() {
return g.call(null, f.apply(null, argsArray(arguments)))
}
}
// compose : ((y -> z), (x -> y), ..., (a -> b)) -> a -> z
function compose() {
if(!arguments.length) {
throw new TypeError('compose: At least one function required')
throw new TypeError(err)
}

const fns =
argsArray(arguments)
argsArray(arguments).slice().reverse()

if(fns.filter(x => !isFunction(x)).length) {
throw new TypeError('compose: Only accepts functions')
const head =
fns[0]

if(!isFunction(head)) {
throw new TypeError(err)
}

return pipe.apply(null, fns.slice().reverse())
const tail =
fns.slice(1).concat(identity)

return tail.reduce(applyPipe, head)
}

module.exports = compose
38 changes: 24 additions & 14 deletions helpers/compose.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,30 @@ test('compose parameters', t => {

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

t.throws(compose, TypeError, 'throws Error when nothing passed')

t.throws(c(undefined), TypeError, 'throws TypeError when undefined passed')
t.throws(c(null), TypeError, 'throws TypeError when null passed')

t.throws(c(''), TypeError, 'throws TypeError when falsey string passed')
t.throws(c('string'), TypeError, 'throws TypeError when truthy string passed')
t.throws(c(0), TypeError, 'throws TypeError when falsy number passed')
t.throws(c(1), TypeError, 'throws TypeError when truthy number passed')
t.throws(c(false), TypeError, 'throws TypeError when false passed')
t.throws(c(true), TypeError, 'throws TypeError when true passed')

t.throws(c({}), TypeError, 'throws TypeError when object passed')
t.throws(c([]), TypeError, 'throws TypeError when array passed')
const err = /compose: Functions required/
t.throws(compose, err, 'throws Error when nothing passed')

t.throws(c(undefined), err, 'throws when undefined passed')
t.throws(c(null), err, 'throws when null passed')
t.throws(c(''), err, 'throws when falsey string passed')
t.throws(c('string'), err, 'throws when truthy string passed')
t.throws(c(0), err, 'throws when falsy number passed')
t.throws(c(1), err, 'throws when truthy number passed')
t.throws(c(false), err, 'throws when false passed')
t.throws(c(true), err, 'throws when true passed')
t.throws(c({}), err, 'throws when object passed')
t.throws(c([]), err, 'throws when array passed')

t.throws(c(undefined, unit), err, 'throws when undefined passed as second argument')
t.throws(c(null, unit), err, 'throws when null passed as second argument')
t.throws(c('', unit), err, 'throws when falsey string passed as second argument')
t.throws(c('string', unit), err, 'throws when truthy string passed as second argument')
t.throws(c(0, unit), err, 'throws when falsy number passed as second argument')
t.throws(c(1, unit), err, 'throws when truthy number passed as second argument')
t.throws(c(false, unit), err, 'throws when false passed as second argument')
t.throws(c(true, unit), err, 'throws when true passed as second argument')
t.throws(c({}, unit), err, 'throws when object passed as second argument')
t.throws(c([], unit), err, 'throws when array passed as second argument')

t.ok(isFunction(compose(unit)), 'returns a function')

Expand Down
36 changes: 17 additions & 19 deletions helpers/composeP.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@

const argsArray = require('../internal/argsArray')

const isEmpty = require('../predicates/isEmpty')
const identity = require('../combinators/identity')
const isFunction = require('../predicates/isFunction')
const isPromise = require('../predicates/isPromise')

function applyCompose(f, g) {
const err = 'composeP: Promise returning functions required'

function applyPipe(f, g) {
if(!isFunction(g)) {
throw new TypeError(err)
}

return function() {
const p = f.apply(null, arguments)

if(!isPromise(p)) {
throw new TypeError('composeP: Only accepts Promise returning functions')
throw new TypeError(err)
}

return p.then(g)
Expand All @@ -21,31 +27,23 @@ function applyCompose(f, g) {

function composeP() {
if(!arguments.length) {
throw new TypeError('composeP: At least one Promise returning function required')
throw new TypeError(err)
}

const fns =
argsArray(arguments).reverse()

if(fns.filter(x => !isFunction(x)).length) {
throw new TypeError('composeP: Only accepts Promise returning functions')
}

const head =
fns.shift()

if(isEmpty(fns)) {
return function() {
const m = head.apply(null, arguments)
fns[0]

if(!isPromise(m)) {
throw new TypeError('composeP: Only accepts Promise returning functions')
}
return m
}
if(!isFunction(head)) {
throw new TypeError(err)
}

return fns.reduce(applyCompose, head)
const tail =
fns.slice(1).concat(identity)

return tail.reduce(applyPipe, head)
}

module.exports = composeP
52 changes: 31 additions & 21 deletions helpers/composeP.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,48 @@ const isFunction = require('../predicates/isFunction')

const composeP = require('./composeP')

test('composeP parameters', t => {
test('composeP errors', t => {
const prom = x => Promise.resolve(x)

const pp = bindFunc(composeP)
const f = bindFunc(composeP(unit))
const g = bindFunc(composeP(prom, unit))

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

const noArgs = /composeP: At least one Promise returning function required/
t.throws(composeP, noArgs, 'throws when nothing passed')

const noFuncs = /composeP: Only accepts Promise returning functions/
t.throws(f(), noFuncs, 'throws when single function does not return a Promise')
t.throws(g(), noFuncs, 'throws when head function does not return a Promise')

t.throws(pp(undefined), noFuncs, 'throws when undefined passed')
t.throws(pp(null), noFuncs, 'throws when null passed')
t.throws(pp(''), noFuncs, 'throws when falsey string passed')
t.throws(pp('string'), noFuncs, 'throws when truthy string passed')
t.throws(pp(0), noFuncs, 'throws when falsy number passed')
t.throws(pp(1), noFuncs, 'throws when truthy number passed')
t.throws(pp(false), noFuncs, 'throws when false passed')
t.throws(pp(true), noFuncs, 'throws when true passed')
t.throws(pp({}), noFuncs, 'throws when object passed')
t.throws(pp([]), noFuncs, 'throws when array passed')
const err = /composeP: Promise returning functions required/
t.throws(composeP, err, 'throws when nothing passed')

t.throws(f(), err, 'throws when single function does not return a Promise')
t.throws(g(), err, 'throws when head function does not return a Promise')

t.throws(pp(undefined), err, 'throws when undefined passed')
t.throws(pp(null), err, 'throws when null passed')
t.throws(pp(''), err, 'throws when falsey string passed')
t.throws(pp('string'), err, 'throws when truthy string passed')
t.throws(pp(0), err, 'throws when falsy number passed')
t.throws(pp(1), err, 'throws when truthy number passed')
t.throws(pp(false), err, 'throws when false passed')
t.throws(pp(true), err, 'throws when true passed')
t.throws(pp({}), err, 'throws when object passed')
t.throws(pp([]), err, 'throws when array passed')

t.throws(pp(undefined, prom), err, 'throws when undefined passed')
t.throws(pp(null, prom), err, 'throws when null passed')
t.throws(pp('', prom), err, 'throws when falsey string passed')
t.throws(pp('string', prom), err, 'throws when truthy string passed')
t.throws(pp(0, prom), err, 'throws when falsy number passed')
t.throws(pp(1, prom), err, 'throws when truthy number passed')
t.throws(pp(false, prom), err, 'throws when false passed')
t.throws(pp(true, prom), err, 'throws when true passed')
t.throws(pp({}, prom), err, 'throws when object passed')
t.throws(pp([], prom), err, 'throws when array passed')

t.end()
})

test('composeP functionality', t => {
t.plan(5)
t.plan(6)

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

const res = x => Promise.resolve(x)
const rej = x => Promise.reject(x)
Expand Down
16 changes: 11 additions & 5 deletions helpers/pipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ const argsArray = require('../internal/argsArray')
const identity = require('../combinators/identity')
const isFunction = require('../predicates/isFunction')

const err = 'pipe: Functions required'

function applyPipe(f, g) {
if(!isFunction(g)) {
throw new TypeError(err)
}

return function() {
return g.call(null, f.apply(null, argsArray(arguments)))
}
Expand All @@ -14,19 +20,19 @@ function applyPipe(f, g) {
// pipe : ((a -> b), (b -> c), ..., (y -> z)) -> a -> z
function pipe() {
if(!arguments.length) {
throw new TypeError('pipe: At least one function required')
throw new TypeError(err)
}

const fns =
argsArray(arguments)

if(fns.filter(x => !isFunction(x)).length) {
throw new TypeError('pipe: Only accepts functions')
}

const head =
fns[0]

if(!isFunction(head)) {
throw new TypeError(err)
}

const tail =
fns.slice(1).concat(identity)

Expand Down
48 changes: 28 additions & 20 deletions helpers/pipe.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,33 @@ const unit = require('../helpers/unit')

const pipe = require('./pipe')

test('pipe parameters', t => {
test('pipe errors', t => {
const c = bindFunc(pipe)

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

t.throws(pipe, TypeError, 'throws Error when nothing passed')

t.throws(c(undefined), TypeError, 'throws TypeError when undefined passed')
t.throws(c(null), TypeError, 'throws TypeError when null passed')

t.throws(c(''), TypeError, 'throws TypeError when falsey string passed')
t.throws(c('string'), TypeError, 'throws TypeError when truthy string passed')
t.throws(c(0), TypeError, 'throws TypeError when falsy number passed')
t.throws(c(1), TypeError, 'throws TypeError when truthy number passed')
t.throws(c(false), TypeError, 'throws TypeError when false passed')
t.throws(c(true), TypeError, 'throws TypeError when true passed')

t.throws(c({}), TypeError, 'throws TypeError when object passed')
t.throws(c([]), TypeError, 'throws TypeError when array passed')

t.ok(isFunction(pipe(unit)), 'returns a function')
const err = /pipe: Functions required/

t.throws(pipe, err, 'throws when nothing passed')

t.throws(c(undefined), err, 'throws when undefined passed')
t.throws(c(null), err, 'throws when null passed')
t.throws(c(''), err, 'throws when falsey string passed')
t.throws(c('string'), err, 'throws err when truthy string passed')
t.throws(c(0), err, 'throws err when falsy number passed')
t.throws(c(1), err, 'throws err when truthy number passed')
t.throws(c(false), err, 'throws err when false passed')
t.throws(c(true), err, 'throws err when true passed')
t.throws(c({}), err, 'throws err when object passed')
t.throws(c([]), err, 'throws err when array passed')

t.throws(c(unit, undefined), err, 'throws when undefined passed as second argument')
t.throws(c(unit, null), err, 'throws when null passed as second argument')
t.throws(c(unit, ''), err, 'throws when falsey string passed as second argument')
t.throws(c(unit, 'string'), err, 'throws err when truthy string passed as second argument')
t.throws(c(unit, 0), err, 'throws err when falsy number passed as second argument')
t.throws(c(unit, 1), err, 'throws err when truthy number passed as second argument')
t.throws(c(unit, false), err, 'throws err when false passed as second argument')
t.throws(c(unit, true), err, 'throws err when true passed as second argument')
t.throws(c(unit, {}), err, 'throws err when object passed as second argument')
t.throws(c(unit, []), err, 'throws err when array passed as second argument')

t.end()
})
Expand All @@ -44,6 +50,8 @@ test('pipe function', t => {
const args = [ 'first', 'second' ]
const result = pipe(first, second).apply(null, args)

t.ok(isFunction(pipe(unit)), 'returns a function')

t.ok(first.calledBefore(second), 'left-most function is called first')
t.ok(first.calledWith.apply(first, args), 'right-most function applied with all arguments')
t.ok(second.calledWith('string'), 'second function receives result of the first function')
Expand Down
35 changes: 17 additions & 18 deletions helpers/pipeP.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,48 @@

const argsArray = require('../internal/argsArray')

const isEmpty = require('../predicates/isEmpty')
const identity = require('../combinators/identity')
const isFunction = require('../predicates/isFunction')
const isPromise = require('../predicates/isPromise')

const err = 'pipeP: Promise returning functions required'

function applyPipe(f, g) {
if(!isFunction(g)) {
throw new TypeError(err)
}

return function() {
const p = f.apply(null, arguments)

if(!isPromise(p)) {
throw new TypeError('pipeP: Only accepts Promise returning functions')
throw new TypeError(err)
}

return p.then(g)
}
}

// pipeP : Promise p => ((a -> p b), (b -> p c), ..., (y -> p z)) -> a -> p z
function pipeP() {
if(!arguments.length) {
throw new TypeError('pipeP: At least one Promise returning function required')
throw new TypeError(err)
}

const fns =
argsArray(arguments)

if(fns.filter(x => !isFunction(x)).length) {
throw new TypeError('pipeP: Only accepts Promise returning functions')
}

const head =
fns.shift()

if(isEmpty(fns)) {
return function() {
const m = head.apply(null, arguments)
fns[0]

if(!isPromise(m)) {
throw new TypeError('pipeP: Only accepts Promise returning functions')
}
return m
}
if(!isFunction(head)) {
throw new TypeError(err)
}

return fns.reduce(applyPipe, head)
const tail =
fns.slice(1).concat(identity)

return tail.reduce(applyPipe, head)
}

module.exports = pipeP
Loading

0 comments on commit af7e578

Please sign in to comment.