Skip to content

Commit

Permalink
Merge pull request #30 from evilsoft/fix-arrow
Browse files Browse the repository at this point in the history
Fix up `Arrow` `first` and `second` and add them to `Star`
  • Loading branch information
evilsoft authored Jan 22, 2017
2 parents 278d1b1 + 3d40e43 commit 6fe133b
Show file tree
Hide file tree
Showing 16 changed files with 550 additions and 152 deletions.
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const All = require('crocks/monoids/All')
## What is in this?
There are (6) classifications of "things" included in this library:

* Crocks (`crocks/crocks`): These are the ADTs that this library is centered around. They are all Functor based Data Types that provide different computational contexts for working in a more declarative, functional flow. For the most part, a majority of the other bits in `crocks` exist to server these ADTs.
* Crocks (`crocks/crocks`): These are the ADTs that this library is centered around. They are all Functor based Data Types that provide different computational contexts for working in a more declarative, functional flow. For the most part, a majority of the other bits in `crocks` exist to serve these ADTs.

* Monoids (`crocks/monoids`): These helpful ADTs are in a class of their own, not really Functors in their own right (although some can be), they are still very useful in our everyday programming needs. Ever need to Sum a list of Numbers or mix a mess of objects together? This is were you will find the ADTs you need to do that.

Expand Down Expand Up @@ -131,11 +131,16 @@ When you want to branch a computation into two parts, this is the function you w
While the `composeB` can be used to create a composition of two functions, there are times when you want to compose an entire flow together. That is where `compose` is useful. With `compose` you can create a right-to-left composition of functions. It will return you a function that represents your flow. Not really sold on writing flows from right-to-left? Well then, I would recommend reaching for `pipe`.

#### `curry : ((a, b, ...) -> z) -> a -> b -> ... -> z`
Pass this function a function and it will return you a function that can be called in any form that you require until all arguments have been provided. For example if you pass a function: `f : (a, b, c) -> d` you get back a function that can be called in any combination, such as: `f(x, y, z)`, `f(x)(y)(z)`, `f(x, y)(z)`, or even `f(x)(y, z)`. This is great for doing partial application on functions for maximum reusability.
Pass this function a function and it will return you a function that can be called in any form that you require until all arguments have been provided. For example if you pass a function: `f : (a, b, c) -> d` you get back a function that can be called in any combination, such as: `f(x, y, z)`, `f(x)(y)(z)`, `f(x, y)(z)`, or even `f(x)(y, z)`. This is great for doing partial application on functions for maximum re-usability.

#### `curryN : Number -> ((a, b, ...) -> z) -> a -> b -> ... -> z`
When dealing with variable argument functions, there are times when you may want to curry a specific number of arguments. Just pass this function the number of arguments you want to curry as well as the function, and it will not call the wrapped function until all arguments have been passed. This function will ONLY pass the number of arguments that you specified, any remaining arguments are discarded. This is great for limiting the arity of a given function when additional parameters are defaulted or not needed. This function will not auto curry functions returning functions like `curry` does, use with `curry` if you need to do some fancy setup for your wrapped function's api.

#### `fanout : (a -> b) -> (a -> c) -> (a -> Pair b c)`
#### `fanout : Arrow a b -> Arrow a c -> Arrow a (Pair b c)`
#### `fanout : Monad m => Star a (m b) -> Star a (m c) -> Star a (m (Pair b c))`
There are may times that you need to keep some running or persistent state while performing a given computation. A common way to do this is to take the input to the computation and branch it into a `Pair` and perform different operations on each version of the input. This is such a common pattern that it warrents the `fanout` function to take care of the initial split and mapping. Just provide a pair of either simple functions or a pair of one of the computation types (`Arrow` or `Star`). You will get back something of the same type that is configured to split it's input into a pair and than apply the first Function/ADT to the first portion of the underlying `Pair` and the second on the second.

#### `ifElse : ((a -> Boolean) | Pred) -> (a -> b) -> (a -> c) -> a -> b | c`
Whenever you need to modify a value based some condition and want a functional way to do it without some imperative `if` statement, then reach for `ifElse`. This function take a predicate (some function that returns a Boolean) and two functions. The first is what is executed when the predicate is true, the second on a false condition. This will return a function ready to take a value to run through the predicate. After the value is evaluated, it will be ran through it's corresponding function, returning the result as the final result. This function comes in really handy when creating lifting functions for Sum Types (like `Either` or `Maybe`).

Expand Down Expand Up @@ -182,7 +187,7 @@ All functions in this group have a signature of `a -> Boolean` and are easily us
* `isApply`: an ADT that provides `map` and `ap` functions
* `isArray`: Array
* `isBoolean`: Boolean
* `isEmpty`: Empty Object, Array or String; All falsey values
* `isEmpty`: Empty Object, Array or String
* `isFunction`: Function
* `isFunctor`: an ADT that provides a `map` function
* `isInteger`: Integer
Expand Down Expand Up @@ -239,7 +244,7 @@ These functions provide a very clean way to build out very simple functions and
| `evalWith` | `a -> m -> b` |
| `execWith` | `a -> m -> b` |
| `filter` | `((a -> Boolean) | Pred) -> m a -> m a` |
| `first` | `(a -> b) -> m (a, c) -> m (b, c)` |
| `first` | `m (a -> b) -> m ((a, c) -> (b, c))` |
| `fst` | `m a b -> a` |
| `head` | `m a -> Maybe a` |
| `log` | `m a b -> a` |
Expand All @@ -252,7 +257,7 @@ These functions provide a very clean way to build out very simple functions and
| `reduce` | `(b -> a -> b) -> b -> m a -> b` |
| `run` | `m a -> b` |
| `runWith` | `a -> m -> b` |
| `second` | `(b -> c) -> m (a, b) -> m (a, c)` |
| `second` | `m (a -> b) -> m ((c, a) -> (c, b))` |
| `sequence` | `Applicative f => (b -> f b) -> m (f a) -> f (m a)` |
| `snd` | `m a b -> b` |
| `swap` | `m a b -> m b a` |
Expand All @@ -267,14 +272,14 @@ These functions provide a very clean way to build out very simple functions and
| `bimap` | `Async`, `Either`, `Pair` |
| `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`, `String`, `Sum`, `Unit` |
| `concat` | `All`, `Any`, `Array`, `Arrow`, `Assign`, `Const`, `List`, `Max`, `Min`, `Pair`, `Pred`, `Prod`, `Star`, `String`, `Sum`, `Unit` |
| `cons` | `Array`, `List` |
| `contramap` | `Arrow`, `Pred`, `Star` |
| `either` | `Either`, `Maybe` |
| `evalWith` | `State` |
| `execWith` | `State` |
| `filter` | `Array`, `List` |
| `first` | `Arrow` |
| `first` | `Arrow`, `Function`, `Star` |
| `fst` | `Pair` |
| `head` | `Array`, `List` |
| `log` | `Writer` |
Expand All @@ -287,7 +292,7 @@ These functions provide a very clean way to build out very simple functions and
| `reduce` | `Array`, `List` |
| `run` | `IO` |
| `runWith` | `Arrow`, `Pred`, `Reader`, `Star`, `State` |
| `second` | `Arrow` |
| `second` | `Arrow`, `Function`, `Star` |
| `sequence` | `Array`, `Either`, `Identity`, `List`, `Maybe` |
| `snd` | `Pair` |
| `swap` | `Async`, `Either`, `Pair` |
Expand Down
3 changes: 3 additions & 0 deletions crocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const helpers = {
compose: require('./helpers/compose'),
curry: require('./helpers/curry'),
curryN: require('./helpers/curryN'),
fanout: require('./helpers/fanout'),
ifElse: require('./helpers/ifElse'),
liftA2: require('./helpers/liftA2'),
liftA3: require('./helpers/liftA3'),
Expand Down Expand Up @@ -73,6 +74,7 @@ const pointFree = {
execWith: require('./pointfree/execWith'),
either: require('./pointfree/either'),
filter: require('./pointfree/filter'),
first: require('./pointfree/first'),
fst: require('./pointfree/fst'),
head: require('./pointfree/head'),
log: require('./pointfree/log'),
Expand All @@ -85,6 +87,7 @@ const pointFree = {
reduce: require('./pointfree/reduce'),
run: require('./pointfree/run'),
runWith: require('./pointfree/runWith'),
second: require('./pointfree/second'),
sequence: require('./pointfree/sequence'),
snd: require('./pointfree/snd'),
swap: require('./pointfree/swap'),
Expand Down
6 changes: 6 additions & 0 deletions crocks.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const branch = require('./helpers/branch')
const compose = require('./helpers/compose')
const curry = require('./helpers/curry')
const curryN = require('./helpers/curryN')
const fanout = require('./helpers/fanout')
const ifElse = require('./helpers/ifElse')
const liftA2 = require('./helpers/liftA2')
const liftA3 = require('./helpers/liftA3')
Expand Down Expand Up @@ -47,6 +48,7 @@ const either = require('./pointfree/either')
const evalWith = require('./pointfree/evalWith')
const execWith = require('./pointfree/execWith')
const filter = require('./pointfree/filter')
const first = require('./pointfree/first')
const fst = require('./pointfree/fst')
const head = require('./pointfree/head')
const log = require('./pointfree/log')
Expand All @@ -57,6 +59,7 @@ const read = require('./pointfree/read')
const reduce = require('./pointfree/reduce')
const run = require('./pointfree/run')
const runWith = require('./pointfree/runWith')
const second = require('./pointfree/second')
const snd = require('./pointfree/snd')
const tail = require('./pointfree/tail')
const value = require('./pointfree/value')
Expand Down Expand Up @@ -112,6 +115,7 @@ test('entry', t => {
t.equal(crocks.compose, compose, 'provides the compose function')
t.equal(crocks.curry, curry, 'provides the curry function')
t.equal(crocks.curryN, curryN, 'provides the curryN function')
t.equal(crocks.fanout, fanout, 'provides the fanout function')
t.equal(crocks.ifElse, ifElse, 'provides the ifElse function')
t.equal(crocks.liftA2, liftA2, 'provides the liftA2 function')
t.equal(crocks.liftA3, liftA3, 'provides the liftA3 function')
Expand Down Expand Up @@ -146,6 +150,7 @@ test('entry', t => {
t.equal(crocks.evalWith, evalWith, 'provides the evalWith point-free function')
t.equal(crocks.execWith, execWith, 'provides the execWith point-free function')
t.equal(crocks.filter, filter, 'provides the filter point-free function')
t.equal(crocks.first, first, 'provides the first point-free function')
t.equal(crocks.fst, fst, 'provides the fst point-free function')
t.equal(crocks.head, head, 'provides the head point-free function')
t.equal(crocks.log, log, 'provides the log point-free function')
Expand All @@ -156,6 +161,7 @@ test('entry', t => {
t.equal(crocks.read, read, 'provides the read point-free function')
t.equal(crocks.run, run, 'provides the run point-free function')
t.equal(crocks.runWith, runWith, 'provides the runWith point-free function')
t.equal(crocks.second, second, 'provides the second point-free function')
t.equal(crocks.snd, snd, 'provides the snd point-free function')
t.equal(crocks.tail, tail, 'provides the tail point-free function')
t.equal(crocks.value, value, 'provides the value point-free function')
Expand Down
20 changes: 6 additions & 14 deletions crocks/Arrow.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,30 +68,22 @@ function Arrow(runWith) {
return Arrow(compose(r, runWith, l))
}

function first(fn) {
if(!isFunction(fn)) {
throw new TypeError('Arrow.first: Function required')
}

return map(function(x) {
function first() {
return Arrow(function(x) {
if(!(x && x.type && x.type() === Pair.type())) {
throw TypeError('Arrow.first: Pair required for inner argument')
}
return x.bimap(fn, identity)
return x.bimap(runWith, identity)
})
}

function second(fn) {
if(!isFunction(fn)) {
throw new TypeError('Arrow.second: Function required')
}

return map(function(x) {
function second() {
return Arrow(function(x) {
if(!(x && x.type && x.type() === Pair.type())) {
throw TypeError('Arrow.second: Pair required for inner argument')
}

return x.bimap(identity, fn)
return x.bimap(identity, runWith)
})
}

Expand Down
42 changes: 7 additions & 35 deletions crocks/Arrow.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ test('Arrow promap functionality', t => {
t.end()
})

test('Arrow promap properties (Functor)', t => {
test('Arrow promap properties (Profunctor)', t => {
const m = Arrow(identity)

const f = x => x + 12
Expand Down Expand Up @@ -347,21 +347,9 @@ test('Arrow promap properties (Functor)', t => {
test('Arrow first', t => {
t.ok(isFunction(Arrow(noop).first), 'provides a first function')

const m = Arrow(identity)
const first = bindFunc(m.first)

t.throws(first(undefined), TypeError, 'throws with undefined')
t.throws(first(null), TypeError, 'throws with null')
t.throws(first(0), TypeError, 'throws with falsey number')
t.throws(first(1), TypeError, 'throws with truthy number')
t.throws(first(''), TypeError, 'throws with falsey string')
t.throws(first('string'), TypeError, 'throws with truthy string')
t.throws(first(false), TypeError, 'throws with false')
t.throws(first(true), TypeError, 'throws with true')
t.throws(first([]), TypeError, 'throws with an array')
t.throws(first({}), TypeError, 'throws with an object')
const m = Arrow(x => x + 1)

const runWith = bindFunc(m.first(identity).runWith)
const runWith = bindFunc(m.first().runWith)

t.throws(runWith(undefined), TypeError, 'throws with undefined as inner argument')
t.throws(runWith(null), TypeError, 'throws with null as inner argument')
Expand All @@ -374,11 +362,9 @@ test('Arrow first', t => {
t.throws(runWith([]), TypeError, 'throws with an array as inner argument')
t.throws(runWith({}), TypeError, 'throws with an object as inner argument')

t.doesNotThrow(first(identity), 'does not throw when passed a function')
t.doesNotThrow(runWith(Pair.of(2)), 'does not throw when inner value is a Pair')

const fn = x => x + 1
const result = m.first(fn).runWith(Pair(10, 10))
const result = m.first().runWith(Pair(10, 10))

t.equal(result.type(), 'Pair', 'returns a Pair')
t.equal(result.fst(), 11, 'applies the function to the fst element of a pair')
Expand All @@ -390,21 +376,9 @@ test('Arrow first', t => {
test('Arrow second', t => {
t.ok(isFunction(Arrow(noop).second), 'provides a second function')

const m = Arrow(identity)
const second = bindFunc(m.second)

t.throws(second(undefined), TypeError, 'throws with undefined')
t.throws(second(null), TypeError, 'throws with null')
t.throws(second(0), TypeError, 'throws with falsey number')
t.throws(second(1), TypeError, 'throws with truthy number')
t.throws(second(''), TypeError, 'throws with falsey string')
t.throws(second('string'), TypeError, 'throws with truthy string')
t.throws(second(false), TypeError, 'throws with false')
t.throws(second(true), TypeError, 'throws with true')
t.throws(second([]), TypeError, 'throws with an array')
t.throws(second({}), TypeError, 'throws with an object')
const m = Arrow(x => x + 1)

const runWith = bindFunc(m.second(identity).runWith)
const runWith = bindFunc(m.second().runWith)

t.throws(runWith(undefined), TypeError, 'throws with undefined as inner argument')
t.throws(runWith(null), TypeError, 'throws with null as inner argument')
Expand All @@ -417,11 +391,9 @@ test('Arrow second', t => {
t.throws(runWith([]), TypeError, 'throws with an array as inner argument')
t.throws(runWith({}), TypeError, 'throws with an object as inner argument')

t.doesNotThrow(second(identity), 'does not throw when passed a function')
t.doesNotThrow(runWith(Pair.of(2)), 'does not throw when inner value is a Pair')

const fn = x => x + 1
const result = m.second(fn).runWith(Pair(10, 10))
const result = m.second().runWith(Pair(10, 10))

t.equal(result.type(), 'Pair', 'returns a Pair')
t.equal(result.snd(), 11, 'applies the function to the snd element of a pair')
Expand Down
69 changes: 65 additions & 4 deletions crocks/Star.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@

const isFunction = require('../predicates/isFunction')
const isFunctor = require('../predicates/isFunctor')
const isMonad = require('../predicates/isMonad')

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

const identity = require('../combinators/identity')
const compose = require('../helpers/compose')
const constant = require('../combinators/constant')

const Pair = require('./Pair')

const _type =
constant('Star')

Expand All @@ -23,6 +28,30 @@ function Star(runWith) {
const inspect =
constant(`Star${_inspect(runWith)}`)

function concat(s) {
if(!(s && isType(type(), s))) {
throw new TypeError('Star.concat: Star required')
}

return Star(function(x) {
const m = runWith(x)

if(!isMonad(m)) {
throw new TypeError('Star.concat: Computations must return a Monad')
}

return m.chain(function(val) {
const inner = s.runWith(val)

if(!(inner && isType(m.type(), inner))) {
throw new TypeError('Star.concat: Computations must return Monads of the same type')
}

return inner
})
})
}

function map(fn) {
if(!isFunction(fn)) {
throw new TypeError('Star.map: Function required')
Expand All @@ -32,7 +61,7 @@ function Star(runWith) {
const m = runWith(x)

if(!isFunctor(m)) {
throw new TypeError('Star.map: Internal function must return a Functor')
throw new TypeError('Star.map: Computation must return a Functor')
}

return m.map(fn)
Expand All @@ -56,16 +85,48 @@ function Star(runWith) {
const m = runWith(l(x))

if(!isFunctor(m)) {
throw new TypeError('Star.promap: Internal function must return a Functor')
throw new TypeError('Star.promap: Computation must return a Functor')
}

return m.map(r)
})
}

function first() {
return Star(function(x) {
if(!isType(Pair.type(), x)) {
throw TypeError('Star.first: Pair required for computation input')
}

const m = runWith(x.fst())

if(!isFunctor(m)) {
throw new TypeError('Star.first: Computaion must return a Functor')
}

return m.map(l => Pair(l, x.snd()))
})
}

function second() {
return Star(function(x) {
if(!isType(Pair.type(), x)) {
throw TypeError('Star.second: Pair required for computation input')
}

const m = runWith(x.snd())

if(!isFunctor(m)) {
throw new TypeError('Star.second: Computation must return a Functor')
}

return m.map(r => Pair(x.fst(), r))
})
}

return {
inspect, type, runWith, map,
contramap, promap
inspect, type, runWith, concat, map,
contramap, promap, first, second
}
}

Expand Down
Loading

0 comments on commit 6fe133b

Please sign in to comment.