making the code ~~worse~~ Very Better

This commit is contained in:
Rowan 2024-11-22 02:54:55 -06:00
parent 18a0f8656b
commit ceab51b145
2 changed files with 139 additions and 65 deletions

109
src/fn.js
View file

@ -1,8 +1,11 @@
import { Result } from './result.js' import { Result } from './result.js'
export function curry(func) { export const identity = x => x
// functions
export function curryN(arity, func) {
return function curried(...args) { return function curried(...args) {
if (args.length >= func.length) { if (args.length >= arity) {
return func.apply(this, args) return func.apply(this, args)
} else { } else {
return function(...args2) { return function(...args2) {
@ -12,41 +15,97 @@ export function curry(func) {
} }
} }
export const curry = fn => curryN(fn.length, fn)
export const flip = fn => curry((a, b) => fn(b, a)) export const flip = fn => curry((a, b) => fn(b, a))
export const tap = curry((fn, val) => {
fn(val)
return val
})
// predicates export const always = x => () => x
export const and = (...booleans) => booleans.every(identity) export const pipe = (...f) => v => reduce(applyTo, v, f)
export const or = (...booleans) => booleans.some(identity) export const apply = fn => args => fn.apply(null, args)
export const isOk = r => !is(Result, r) || r.isOk() export const applyTo = curry((v, fn) => fn(v))
export const useWith = curry((fn, tfns) =>
curryN(
tfns.length,
(...args) => pipe(enumerate, map(([i, a]) => tfns[i](a)), fn)(args)
))
export const construct = Type => args => new Type(...args) export const converge = (fn, [head, ...rest]) => curryN(
head.length,
(...args) => pipe(...rest, fn)(head(...args))
)
export const of = value => Array.isArray(value) ? value : [value] export const diverge = (fn, tfns) => pipe(applyTo, flip(map)(tfns), apply(fn))
export const concat = curry((a, b) => b.concat(a))
export const head = value => value[0]
export const tail = value => value.slice(1)
export const prop = curry((p, v) => v[p])
export const apply = curry((v, f) => f(v))
export const reduce = curry((fn, init, v) => v.reduce(fn, init))
export const map = curry((fn, v) => v.map(fn))
export const filter = curry((fn, v) => v.filter(fn))
export const find = curry((fn, v) => v.find(fn))
export const join = curry((sep, v) => v.join(sep))
export const rev = v => v.slice().reverse()
export const pipe = (...f) => v => f.reduce(apply, v)
export const prepend = curry((value, iter) => [value, ...iter])
export const append = curry((value, iter) => [...iter, value])
export const identity = x => x // types
export const isArray = Array.isArray
export const isNil = v => !v
export const is = curry((type, value) => value instanceof type)
// branching
export const ifElse = curry((p, ft, ff, v) => p(v) ? ft(v) : ff(v)) export const ifElse = curry((p, ft, ff, v) => p(v) ? ft(v) : ff(v))
export const when = curry((p, f, v) => ifElse(p, f, identity, v)) export const when = curry((p, f, v) => ifElse(p, f, identity, v))
export const unless = curry((p, f, v) => ifElse(p, identity, f, v)) export const unless = curry((p, f, v) => ifElse(p, identity, f, v))
export const isNil = v => !v // predicates
export const and = (...booleans) => booleans.every(identity)
export const or = (...booleans) => booleans.some(identity)
export const isOk = value => !is(Result, value) || value.isOk()
// classes
export const construct = Type => args => new Type(...args)
// strings
export const split = curry((delim, v) => v.split(delim))
// arrays
export const length = v => v.length
export const len = length
export const of = unless(isArray, value => ([value]))
export const concat = curry((a, b) => b.concat(a))
export const take = curry((n, value) => value.slice(0, n)) export const take = curry((n, value) => value.slice(0, n))
export const drop = curry((n, value) => value.slice(n)) export const drop = curry((n, value) => value.slice(n))
export const nth = curry((index, value) => value[index])
export const head = nth(0)
export const tail = drop(1)
export const map = curry((fn, v) => v.map(fn))
export const filter = curry((fn, v) => v.filter(fn))
export const reduce = curry((fn, init, v) => v.reduce(fn, init))
export const find = curry((fn, v) => v.find(fn))
export const join = curry((sep, v) => v.join(sep))
export const rev = v => v.slice().reverse()
export const prepend = curry((value, iter) => [value, ...iter])
export const append = curry((value, iter) => [...iter, value])
export const enumerate = value => value.map((v, i) => ([i, v]))
export const flatten = curry((n, v) => v.flat(n))
export const update = curry((index, value, arr) => arr.toSpliced(index, 1, value))
export const is = curry((type, value) => value instanceof type) // objects
const makePath = unless(isArray, split('.'))
export const assoc = curry((key, value, obj) => ({ ...obj, [key]: value }))
export const assocPath = curry((key, value, obj) => pipe(
makePath,
rev,
reduce((acc, val) => fromEntries([[val, acc]]), value),
mergeLeft(obj)
)(key))
export const prop = curry((key, obj) => obj && key && obj[key])
export const path = curry((key, obj) => reduce(flip(prop), obj, makePath(key)))
export const mergeLeft = curry((a, b) => ({ ...b, ...a }))
export const mergeRight = flip(mergeLeft)
export const keys = Object.keys
export const values = Object.values
export const entries = Object.entries
export const fromEntries = Object.fromEntries
// lenses
export const lens = curry((get, set) => ({ get, set }))
export const lensPath = path => lens(path(path), assocPath(path))
export const lensIndex = index => lens(nth(index), update(index))
export const view = curry(({ get }, obj) => get(obj))
export const set = curry((l, value, obj) => over(l, always(value), obj))
export const over = curry(({ get, set }, fn, obj) => set(fn(get(obj)), obj))

View file

@ -1,7 +1,7 @@
import { defineQuery, hasComponent } from 'bitecs' import { defineQuery, hasComponent } from 'bitecs'
import { query as q } from './query-parser/index.js' import { query as q } from './query-parser/index.js'
import { parseAll } from './parser.js' import { parseAll } from './parser.js'
import { curry, of, is, reduce, flip, concat, map } from './fn.js' import { curry, of, is, reduce, flip, concat, map, when, assoc, mergeLeft, isNil, path, prop, ifElse, diverge, pipe, curryN, always, tap } from './fn.js'
import { Relationship } from './query-parser/types.js' import { Relationship } from './query-parser/types.js'
/* /*
@ -16,7 +16,7 @@ const prepareQuery = (query, component) => {
if (relationship) { if (relationship) {
const { from: f, to: t, edge: e } = match const { from: f, to: t, edge: e } = match
const [from, to, edge] = [f, t, e].map(q => prepareQuery({ match: q, returnValues }, component)) const [from, to, edge] = map(q => prepareQuery({ match: q, returnValues }, component), [f, t, e])
return { from, to, edge, relationship, schema: query } return { from, to, edge, relationship, schema: query }
} else { } else {
@ -60,10 +60,11 @@ const queryRelationship = (from, edge, to, world) => {
} }
const cat = flip(concat) const cat = flip(concat)
const assembleQuery = (query, entities) => map(entity => of({ entity, query }), entities)
const executeQuery = (query, world) => { const executeQuery = curry((query, world) => {
if (!query.relationship) { if (!query.relationship) {
return map(id => ({ [id]: query }), query.query(world)) return assembleQuery(query, query.query(world))
} }
const { from, to: _to, edge } = query const { from, to: _to, edge } = query
@ -84,53 +85,67 @@ const executeQuery = (query, world) => {
..._to, ..._to,
from: { from: {
...to, ...to,
query: () => of(Edge.to[eid]) query: always(of(Edge.to[eid]))
} }
} }
const results = map(cat([eid]), executeQuery(next, world)) return pipe(
const expanded = map( executeQuery(next),
([l, r]) => ({ [l]: query, ...r }), map(cat([eid])),
results map(([l, r]) => ([{ entity: l, query }, r])),
) x => ([...acc, ...x]),
return [...acc, ...expanded] )(world)
}, [], edges) }, [], edges)
} else { } else {
return map(id => of({ [id]: query }), edges) return assembleQuery(query, edges)
}
} }
})
const val = v => v?.value?.value const returnValues = pipe(path('query.schema.returnValues.values'), map(prop('value')))
const include = returns => name => name != null && returns.includes(name) const includes = curry((val, arr) => !isNil(val) && arr.includes(val))
const resolveReturns = results => { const maybeAssoc = curry((pred, key, value, obj) =>
return map(result => { when(pred, assoc(key, value), obj)
return reduce((acc, [id, query]) => { )
const q = query.schema.match
const inc = include(query.schema.returnValues.values.map(x => x.value)) const resolveNode = curry((returns, name, entity, obj) =>
if (is(Relationship, q)) { maybeAssoc(always(includes(name, returns)), name, entity, obj)
)
const resolveRelationship = edges => {
const { entity, query } = edges
const Edge = query.edge.type const Edge = query.edge.type
const fn = val(q.from.name) const returns = returnValues(edges)
const en = val(q.edge.name)
const tn = val(q.to.name) const queryProp = flip(prop)(query)
return { const [from, edge, to] = map(queryProp, ['from', 'edge', 'to'])
...acc, ...{ const [fn, en] = [from.name, edge.name]
...(inc(fn) && { [fn]: Edge.from[id] }), const tn = ifElse(prop('relationship'),
...(inc(en) && { [en]: parseInt(id, 10) }), path('from.name'),
...(inc(tn) && { [tn]: Edge.to[id] }) prop('name'),
} to
} )
} else {
const name = val(q.name) return pipe(
return { ...acc, ...(inc(name) && { [name]: id }) } resolveNode(returns, fn, Edge.from[entity]),
} resolveNode(returns, en, entity),
}, {}, Object.entries(result)) resolveNode(returns, tn, Edge.to[entity]),
}, results) )({})
} }
const resolveReturns = map(
pipe(
map(ifElse(
path('query.relationship'),
resolveRelationship,
diverge(resolveNode, [returnValues, path('query.schema.match.name.value.value'), prop('entity'), always({})])
)),
reduce(mergeLeft, {})
)
)
export const query = curry((input, engine) => { export const query = curry((input, engine) => {
return parseAll(q, input).map(({ use, ...rest }) => { return parseAll(q, input).map(({ use, ...rest }) => {
const worldName = use && use.value || 'default' const worldName = use && use.value || 'default'
const world = engine.world[worldName] const world = engine.world[worldName]
const prepared = prepareQuery(rest, engine.component) const prepared = prepareQuery(rest, engine.component)