From ceab51b145df298d18797a09bfbd543015cc3927 Mon Sep 17 00:00:00 2001 From: kitsunecafe Date: Fri, 22 Nov 2024 02:54:55 -0600 Subject: [PATCH] making the code ~~worse~~ Very Better --- src/fn.js | 109 +++++++++++++++++++++++++++++++++++++++------------ src/query.js | 95 +++++++++++++++++++++++++------------------- 2 files changed, 139 insertions(+), 65 deletions(-) diff --git a/src/fn.js b/src/fn.js index 573c706..ff6fe3b 100644 --- a/src/fn.js +++ b/src/fn.js @@ -1,8 +1,11 @@ import { Result } from './result.js' -export function curry(func) { +export const identity = x => x + +// functions +export function curryN(arity, func) { return function curried(...args) { - if (args.length >= func.length) { + if (args.length >= arity) { return func.apply(this, args) } else { 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 tap = curry((fn, val) => { + fn(val) + return val +}) -// predicates -export const and = (...booleans) => booleans.every(identity) -export const or = (...booleans) => booleans.some(identity) -export const isOk = r => !is(Result, r) || r.isOk() +export const always = x => () => x +export const pipe = (...f) => v => reduce(applyTo, v, f) +export const apply = fn => args => fn.apply(null, args) +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 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 diverge = (fn, tfns) => pipe(applyTo, flip(map)(tfns), apply(fn)) -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 when = curry((p, f, v) => ifElse(p, f, identity, 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 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)) diff --git a/src/query.js b/src/query.js index e8ef8f5..fa965ae 100644 --- a/src/query.js +++ b/src/query.js @@ -1,7 +1,7 @@ import { defineQuery, hasComponent } from 'bitecs' import { query as q } from './query-parser/index.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' /* @@ -16,7 +16,7 @@ const prepareQuery = (query, component) => { if (relationship) { 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 } } else { @@ -60,10 +60,11 @@ const queryRelationship = (from, edge, to, world) => { } 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) { - return map(id => ({ [id]: query }), query.query(world)) + return assembleQuery(query, query.query(world)) } const { from, to: _to, edge } = query @@ -84,53 +85,67 @@ const executeQuery = (query, world) => { ..._to, from: { ...to, - query: () => of(Edge.to[eid]) + query: always(of(Edge.to[eid])) } } - const results = map(cat([eid]), executeQuery(next, world)) - const expanded = map( - ([l, r]) => ({ [l]: query, ...r }), - results - ) - return [...acc, ...expanded] + return pipe( + executeQuery(next), + map(cat([eid])), + map(([l, r]) => ([{ entity: l, query }, r])), + x => ([...acc, ...x]), + )(world) }, [], edges) } else { - return map(id => of({ [id]: query }), edges) + return assembleQuery(query, edges) } +}) + +const returnValues = pipe(path('query.schema.returnValues.values'), map(prop('value'))) +const includes = curry((val, arr) => !isNil(val) && arr.includes(val)) + +const maybeAssoc = curry((pred, key, value, obj) => + when(pred, assoc(key, value), obj) +) + +const resolveNode = curry((returns, name, entity, obj) => + maybeAssoc(always(includes(name, returns)), name, entity, obj) +) + +const resolveRelationship = edges => { + const { entity, query } = edges + const Edge = query.edge.type + const returns = returnValues(edges) + + const queryProp = flip(prop)(query) + const [from, edge, to] = map(queryProp, ['from', 'edge', 'to']) + const [fn, en] = [from.name, edge.name] + const tn = ifElse(prop('relationship'), + path('from.name'), + prop('name'), + to + ) + + return pipe( + resolveNode(returns, fn, Edge.from[entity]), + resolveNode(returns, en, entity), + resolveNode(returns, tn, Edge.to[entity]), + )({}) } -const val = v => v?.value?.value -const include = returns => name => name != null && returns.includes(name) - -const resolveReturns = results => { - return map(result => { - return reduce((acc, [id, query]) => { - const q = query.schema.match - const inc = include(query.schema.returnValues.values.map(x => x.value)) - if (is(Relationship, q)) { - const Edge = query.edge.type - const fn = val(q.from.name) - const en = val(q.edge.name) - const tn = val(q.to.name) - return { - ...acc, ...{ - ...(inc(fn) && { [fn]: Edge.from[id] }), - ...(inc(en) && { [en]: parseInt(id, 10) }), - ...(inc(tn) && { [tn]: Edge.to[id] }) - } - } - } else { - const name = val(q.name) - return { ...acc, ...(inc(name) && { [name]: id }) } - } - }, {}, Object.entries(result)) - }, 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) => { return parseAll(q, input).map(({ use, ...rest }) => { - const worldName = use && use.value || 'default' const world = engine.world[worldName] const prepared = prepareQuery(rest, engine.component)