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

Feature/groupoid #63

Closed
wants to merge 15 commits into from
Closed
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# futil
# futil
[![Greenkeeper badge](https://badges.greenkeeper.io/smartprocure/futil-js.svg)](https://greenkeeper.io/)
[![npm version](https://badge.fury.io/js/futil-js.svg)](https://badge.fury.io/js/futil-js)
![dependencies](https://david-dm.org/smartprocure/futil-js.svg)
Expand Down Expand Up @@ -157,3 +157,10 @@ Maps a function over a recursive iterable. Works by default for nested Arrays, n
nested Arrays and Plain Objects. Also works for any other iterable data type as long as
two other values are sent: a mapping function, and a type checker (See the
unit tests for deepMap).

## groupoid
`groupoid :: (a -> b ... -> c) -> [a] -> [b]`
General variable automata is the definition of
non-deterministic input iterators over discrete state changes,
aka groupoid category.
See: <https://en.wikipedia.org/wiki/Automata_theory#Connection_to_category_theory>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "futil-js",
"version": "1.8.3",
"version": "1.9.0",
"description": "F(unctional) util(ities). Resistance is futile.",
"main": "lib/futil-js.js",
"scripts": {
Expand Down
70 changes: 70 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,76 @@ export const map = _.curry((f, x) => (_.isArray(x) ? _.map : _.mapValues)(f, x))
export const deepMap = _.curry((fn, obj, _map = map, is = isTraversable) =>
_map(e => is(e) ? deepMap(fn, fn(e), _map, is) : e, obj))

// General variable automata is the definition of
// non-deterministic input iterators over discrete state changes
// aka groupoid category
// See: https://en.m.wikipedia.org/wiki/Automata_theory#Connection_to_category_theory
// How to use it:
// groupoid(reducer1, reducer2, ...reducerN)(field, [optional params, see below])
// Each reducer will receive:
// - The result of the previous reducer
// - The current value
// - The key where the current value is located
// - The key names of the parents of the current key
export const groupoid = (...funs) => function G (
// REQUIRED
field, // input n-dimensional field
// Optional parameters
acc = [], // accumulator
breadth, // breadth first is falsy by default
orientation = 1, // > 0 by default, goes bottom to top if < 0 // TODO: Expose a groupoidRight to make this easier
path = [] // only used for recursion purposes, to expose the parent keys of the current key value pair
) {
// return the current accumulator if the field is not iterable
if (!field || typeof field !== 'object') return acc

let accepted = acc
let state = acc
let keys = Object.keys(field)
let fN = 0
let nextBreadth = []

if (orientation < 0) keys = keys.reverse()
let key = keys.shift()

// stops only if the state resulting from funs[fN] is exactly falsee
// (or if (!key) break)
while (state !== false) {
accepted = state
let f = funs[fN]
// if we have no more functins to look at,
// get the next key and reset the function
// counter. Lets us avoid having two whiles.
if (!f) {
key = keys.shift()
fN = 0
f = funs[fN]
}
if (!key) break
let val = field[key]
// result of reducer f[fN]
let result = f(state, val, key, path)
// Either we accumulate iterables to go deeper
// after all the current level (breadth),
// or we go recursive right now.
if (breadth && val && typeof val === 'object') {
nextBreadth.push({ val, key })
} else {
result = G(val, result, breadth, orientation, path.concat(key))
}
if (result !== true) state = result
fN++
}
// if breadth first, going recursive on what was nested in this layer
let result = accepted
while (result && nextBreadth.length) {
let { val, key } = nextBreadth.shift()
result = G(val, accepted, breadth, orientation, path.concat(key))
if (result && result !== true) accepted = result
}
return accepted
}

// Misc
// ----
export const testRegex = regex => regex.test.bind(regex)
Loading