Skip to content

Commit

Permalink
Add converge (Phoenix) to combinators. (#333)
Browse files Browse the repository at this point in the history
* Add `converge` (Phoenix) to combinators.

* Update combinator names for `converge`

* Update `converge` test example usage

* Get famous

* Move `converge` to the correct `combinators` directory and expose

* Add `combinators/converge` to the appropriate docs pages

* Add pirates to `converge` docs

* Add `converge` to `combinators` search terms

* Update helper paths, error message, license date, error checks

* Remove dep destructuring and add signatures to `converge` example code

* Give `converge` example some room to breathe

* Reflect unknown type of `Maybe` in example fn results
  • Loading branch information
amsross authored and evilsoft committed Oct 27, 2018
1 parent e876265 commit eb82706
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 2 deletions.
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,15 @@
"contributions": [
"bug"
]
},
{
"login": "amsross",
"name": "Matt Ross",
"avatar_url": "https://avatars0.githubusercontent.com/u/450220?v=4",
"profile": "http://www.mattross.io",
"contributions": [
"code"
]
}
],
"repoType": "github"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ Thanks goes to these wonderful people ([emoji key][emojis]):
| [<img src="https://avatars1.githubusercontent.com/u/3665793?v=4" width="100px;"/><br /><sub><b>Ian Hofmann-Hicks</b></sub>](https://github.com/evilsoft)<br />[💻](https://github.com/evilsoft/crocks/commits?author=evilsoft "Code") [📖](https://github.com/evilsoft/crocks/commits?author=evilsoft "Documentation") [📹](#video-evilsoft "Videos") | [<img src="https://avatars0.githubusercontent.com/u/19234385?v=4" width="100px;"/><br /><sub><b>Ryan</b></sub>](https://github.com/rstegg)<br />[💻](https://github.com/evilsoft/crocks/commits?author=rstegg "Code") [🐛](https://github.com/evilsoft/crocks/issues?q=author%3Arstegg "Bug reports") [👀](#review-rstegg "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/1271181?v=4" width="100px;"/><br /><sub><b>Andrew Van Slaars</b></sub>](http://vanslaars.io)<br />[📖](https://github.com/evilsoft/crocks/commits?author=avanslaars "Documentation") [📹](#video-avanslaars "Videos") | [<img src="https://avatars0.githubusercontent.com/u/2222191?v=4" width="100px;"/><br /><sub><b>Henrique Limas</b></sub>](https://github.com/HenriqueLimas)<br />[💻](https://github.com/evilsoft/crocks/commits?author=HenriqueLimas "Code") [📖](https://github.com/evilsoft/crocks/commits?author=HenriqueLimas "Documentation") [👀](#review-HenriqueLimas "Reviewed Pull Requests") | [<img src="https://avatars2.githubusercontent.com/u/592876?v=4" width="100px;"/><br /><sub><b>Robert Pearce</b></sub>](https://robertwpearce.com)<br />[🐛](https://github.com/evilsoft/crocks/issues?q=author%3Arpearce "Bug reports") [💻](https://github.com/evilsoft/crocks/commits?author=rpearce "Code") [👀](#review-rpearce "Reviewed Pull Requests") [✅](#tutorial-rpearce "Tutorials") [📖](https://github.com/evilsoft/crocks/commits?author=rpearce "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/888052?v=4" width="100px;"/><br /><sub><b>Scott McCormack</b></sub>](https://github.com/flintinatux)<br />[🐛](https://github.com/evilsoft/crocks/issues?q=author%3Aflintinatux "Bug reports") | [<img src="https://avatars3.githubusercontent.com/u/1706600?v=4" width="100px;"/><br /><sub><b>Fred Daoud</b></sub>](http://www.fdaoud.com)<br />[👀](#review-foxdonut "Reviewed Pull Requests") |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [<img src="https://avatars0.githubusercontent.com/u/8260207?v=4" width="100px;"/><br /><sub><b>Karthik Iyengar</b></sub>](https://github.com/karthikiyengar)<br />[👀](#review-karthikiyengar "Reviewed Pull Requests") [💻](https://github.com/evilsoft/crocks/commits?author=karthikiyengar "Code") [📖](https://github.com/evilsoft/crocks/commits?author=karthikiyengar "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/7376957?v=4" width="100px;"/><br /><sub><b>Jon Whelan</b></sub>](https://github.com/jonwhelan)<br />[🐛](https://github.com/evilsoft/crocks/issues?q=author%3Ajonwhelan "Bug reports") [💻](https://github.com/evilsoft/crocks/commits?author=jonwhelan "Code") | [<img src="https://avatars0.githubusercontent.com/u/1466420?v=4" width="100px;"/><br /><sub><b>Benny Powers</b></sub>](http://bennypowers.com)<br />[📖](https://github.com/evilsoft/crocks/commits?author=bennypowers "Documentation") [💻](https://github.com/evilsoft/crocks/commits?author=bennypowers "Code") [👀](#review-bennypowers "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/1909325?v=4" width="100px;"/><br /><sub><b>Dale Francis</b></sub>](https://github.com/dalefrancis88)<br />[💻](https://github.com/evilsoft/crocks/commits?author=dalefrancis88 "Code") [👀](#review-dalefrancis88 "Reviewed Pull Requests") | [<img src="https://avatars1.githubusercontent.com/u/7926867?v=4" width="100px;"/><br /><sub><b>Premith</b></sub>](https://github.com/premithk)<br />[📖](https://github.com/evilsoft/crocks/commits?author=premithk "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/6302673?v=4" width="100px;"/><br /><sub><b>Dipen Bagia</b></sub>](https://github.com/dbagia)<br />[💡](#example-dbagia "Examples") | [<img src="https://avatars3.githubusercontent.com/u/5243925?v=4" width="100px;"/><br /><sub><b>Andrew Jones</b></sub>](https://github.com/ximenean)<br />[📖](https://github.com/evilsoft/crocks/commits?author=ximenean "Documentation") |
| [<img src="https://avatars0.githubusercontent.com/u/4858051?v=4" width="100px;"/><br /><sub><b>W. K. Seymour III</b></sub>](https://github.com/wayneseymour)<br />[📖](https://github.com/evilsoft/crocks/commits?author=wayneseymour "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/11702749?v=4" width="100px;"/><br /><sub><b>Eliseu Benedito Codinhoto</b></sub>](https://youtube.com/euprogramadoroficial)<br />[📖](https://github.com/evilsoft/crocks/commits?author=zeucxb "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/12712867?v=4" width="100px;"/><br /><sub><b>NiallArkEnergy</b></sub>](https://github.com/NiallArkEnergy)<br />[💻](https://github.com/evilsoft/crocks/commits?author=NiallArkEnergy "Code") [📖](https://github.com/evilsoft/crocks/commits?author=NiallArkEnergy "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/3265934?v=4" width="100px;"/><br /><sub><b>vidyu</b></sub>](https://github.com/vidyu)<br />[📖](https://github.com/evilsoft/crocks/commits?author=vidyu "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/780521?v=4" width="100px;"/><br /><sub><b>Michael Wolfenden</b></sub>](http://michael-wolfenden.github.io)<br />[📖](https://github.com/evilsoft/crocks/commits?author=michael-wolfenden "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/14071356?v=4" width="100px;"/><br /><sub><b>Johan Codinha</b></sub>](https://github.com/JohanCodinha)<br />[🐛](https://github.com/evilsoft/crocks/issues?q=author%3AJohanCodinha "Bug reports") |
| [<img src="https://avatars0.githubusercontent.com/u/4858051?v=4" width="100px;"/><br /><sub><b>W. K. Seymour III</b></sub>](https://github.com/wayneseymour)<br />[📖](https://github.com/evilsoft/crocks/commits?author=wayneseymour "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/11702749?v=4" width="100px;"/><br /><sub><b>Eliseu Benedito Codinhoto</b></sub>](https://youtube.com/euprogramadoroficial)<br />[📖](https://github.com/evilsoft/crocks/commits?author=zeucxb "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/12712867?v=4" width="100px;"/><br /><sub><b>NiallArkEnergy</b></sub>](https://github.com/NiallArkEnergy)<br />[💻](https://github.com/evilsoft/crocks/commits?author=NiallArkEnergy "Code") [📖](https://github.com/evilsoft/crocks/commits?author=NiallArkEnergy "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/3265934?v=4" width="100px;"/><br /><sub><b>vidyu</b></sub>](https://github.com/vidyu)<br />[📖](https://github.com/evilsoft/crocks/commits?author=vidyu "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/780521?v=4" width="100px;"/><br /><sub><b>Michael Wolfenden</b></sub>](http://michael-wolfenden.github.io)<br />[📖](https://github.com/evilsoft/crocks/commits?author=michael-wolfenden "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/14071356?v=4" width="100px;"/><br /><sub><b>Johan Codinha</b></sub>](https://github.com/JohanCodinha)<br />[🐛](https://github.com/evilsoft/crocks/issues?q=author%3AJohanCodinha "Bug reports") | [<img src="https://avatars0.githubusercontent.com/u/450220?v=4" width="100px;"/><br /><sub><b>Matt Ross</b></sub>](http://www.mattross.io)<br />[💻](https://github.com/evilsoft/crocks/commits?author=amsross "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->

### Course/Videos
Expand Down
75 changes: 74 additions & 1 deletion docs/src/pages/docs/functions/combinators.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
description: "Combinators API"
layout: "notopic"
title: "Combinators"
functions: ["applyto", "composeb", "constant", "flip", "identity", "substitution"]
functions: ["applyto", "composeb", "constant", "converge", "flip", "identity", "substitution"]
weight: 10
---

Expand Down Expand Up @@ -48,6 +48,79 @@ 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.

#### converge

`crocks/combinators/converge`

```haskell
converge :: (b -> c -> d) -> (a -> b) -> (a -> c) -> a -> d
```

Provides a means of passing an acculumating function and two branching functions.
A value can be applied to the resulting function which will then be applied to
each branching function, the results of which will be applied to the accumulating
function.

```javascript
import Maybe from 'crocks/Maybe'
import alt from 'crocks/pointfree/alt'
import converge from 'crocks/combinators/converge'
import prop from 'crocks/Maybe/prop'

const { Just } = Maybe

// data :: [ Number ]
const data = [ 1, 2, 3, 4, 5 ]

// divide :: Number -> Number -> Number
const divide = x => y => x / y

// sum :: [ Number ] -> Number
const sum = xs => xs.reduce((m, n) => m + n, 0)

// length :: [ a ] -> Number
const length = xs => xs.length

// average :: [ Number ] -> Number
const average = converge(divide, sum, length)

average(data)
//=> 3

// maybeGetDisplay :: a -> Maybe b
const maybeGetDisplay = prop('display')

// maybeGetFirst :: a -> Maybe b
const maybeGetFirst = prop('first')

// maybeGetLast :: a -> Maybe b
const maybeGetLast = prop('last')

// maybeConcatStrings :: Maybe String -> Maybe String -> Maybe String
const maybeConcatStrings = x => y => Just(x => y => x + ' ' + y).ap(x).ap(y).alt(x).alt(y)

// maybeMakeDisplay :: a -> Maybe String
const maybeMakeDisplay = converge(maybeConcatStrings, maybeGetFirst, maybeGetLast)

// maybeGetName :: a -> Maybe b
const maybeGetName = converge(alt, maybeGetDisplay, maybeMakeDisplay)

maybeGetName({ display: 'Jack Sparrow' })
//=> Just('Jack Sparrow')

maybeGetName({ first: 'J', last: 'S' })
//=> Just('J S')

maybeGetName({ display: 'Jack Sparrow', first: 'J', last: 'S' })
//=> Just('Jack Sparrow')

maybeGetName({ first: 'J' })
//=> Just('J')

maybeGetName({ first: 'S' })
//=> Just('S')
```

#### flip

`crocks/combinators/flip`
Expand Down
1 change: 1 addition & 0 deletions docs/src/pages/docs/functions/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ need to account for for the rest of your flow.
| [`applyTo`][applyto] | `a -> (a -> b) -> b` | `crocks/combinators/applyTo` |
| [`composeB`][composeb] | `(b -> c) -> (a -> b) -> a -> c` | `crocks/combinators/composeB` |
| [`constant`][constant] | `a -> () -> a` | `crocks/combinators/constant` |
| [`converge`][converge] | `(b -> c -> d) -> (a -> b) -> (a -> c) -> a -> d` | `crocks/combinators/converge` |
| [`flip`][flip] | `(a -> b -> c) -> b -> a -> c` | `crocks/combinators/flip` |
| [`identity`][identity] | `a -> a` | `crocks/combinators/identity` |
| [`substitution`][substitution] | `(a -> b -> c) -> (a -> b) -> a -> c` | `crocks/combinators/substitution` |
Expand Down
17 changes: 17 additions & 0 deletions src/combinators/converge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/** @license ISC License (c) copyright 2018 original and current authors */
/** @author Matt Ross (amsross) */

const curry = require('../core/curry')
const isFunction = require('../core/isFunction')

// converge (Phoenix or Starling Prime)
// (b -> c -> d) -> (a -> b) -> (a -> c) -> a -> d
function converge(f, g, h, x) {
if(!isFunction(f) || !isFunction(g) || !isFunction(h)) {
throw new TypeError('converge: Functions required for first three arguments')
}

return curry(f)(g(x), h(x))
}

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

const bindFunc = helpers.bindFunc
const isFunction = require('../core/isFunction')

const sub = require('./converge')

test('converge (Big Phi or S\' combinator)', t => {
const s = bindFunc(sub)
const x = 67
const f = x => y => x + y
const g = x => x - 1
const h = x => x + 1

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

const err = /converge: Functions required for first three arguments/
t.throws(s(undefined, g, h, x), err, 'throws with first arg undefined')
t.throws(s(null, g, h, x), err, 'throws with first arg null')
t.throws(s(0, g, h, x), err, 'throws with first arg falsey number')
t.throws(s(1, g, h, x), err, 'throws with first arg truthy number')
t.throws(s('', g, h, x), err, 'throws with first arg falsey string')
t.throws(s('string', g, h, x), err, 'throws with first arg truthy string')
t.throws(s(false, g, h, x), err, 'throws with first arg false')
t.throws(s(true, g, h, x), err, 'throws with first arg true')
t.throws(s({}, g, h, x), err, 'throws with first arg an object')
t.throws(s([], g, h, x), err, 'throws with first arg an array')

t.throws(s(f, undefined, h, x), err, 'throws with second arg undefined')
t.throws(s(f, null, h, x), err, 'throws with second arg null')
t.throws(s(f, 0, h, x), err, 'throws with second arg falsey number')
t.throws(s(f, 1, h, x), err, 'throws with second arg truthy number')
t.throws(s(f, '', h, x), err, 'throws with second arg falsey string')
t.throws(s(f, 'bling', h, x), err, 'throws with second arg truthy string')
t.throws(s(f, false, h, x), err, 'throws with second arg false')
t.throws(s(f, true, h, x), err, 'throws with second arg true')
t.throws(s(f, {}, h, x), err, 'throws with second arg an object')
t.throws(s(f, [], h, x), err, 'throws with second arg an array')

t.throws(s(f, g, undefined, x), err, 'throws with third arg undefined')
t.throws(s(f, g, null, x), err, 'throws with third arg null')
t.throws(s(f, g, 0, x), err, 'throws with third arg falsey number')
t.throws(s(f, g, 1, x), err, 'throws with third arg truthy number')
t.throws(s(f, g, '', x), err, 'throws with third arg falsey string')
t.throws(s(f, g, 'string', x), err, 'throws with third arg truthy string')
t.throws(s(f, g, false, x), err, 'throws with third arg false')
t.throws(s(f, g, true, x), err, 'throws with third arg true')
t.throws(s(f, g, {}, x), err, 'throws with third arg an object')
t.throws(s(f, g, [], x), err, 'throws with third arg an array')

const result = sub(f, g, h, x)

t.equal(result, 134, 'returns expected result')

t.end()
})
1 change: 1 addition & 0 deletions src/combinators/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module.exports = {
applyTo: require('./applyTo'),
composeB: require('./composeB'),
constant: require('./constant'),
converge: require('./converge'),
flip: require('./flip'),
identity: require('./identity'),
substitution: require('./substitution')
Expand Down

0 comments on commit eb82706

Please sign in to comment.