From cea70e41a53b815b224aacf838a6aaa165c30f03 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 2 Jun 2024 22:24:16 -0300 Subject: data: withReverse{Reference,Contribution}List: factor commonality This doesn't actually move the common behavior into e.g. devoted component compositions - these two still mirror each other - but it *does* isolate the differing behavior in terms of dependencies that are computed uniquely but surrounded by identical compositional steps. This generally seems like a good fit for compositional subroutines, but those aren't official yet. Meanwhile, this is still factored much better than the previous implementation, and hopefully easier to follow as well! --- .../wiki-data/withReverseContributionList.js | 119 +++++++++++++++++---- .../wiki-data/withReverseReferenceList.js | 111 ++++++++++++++++--- 2 files changed, 196 insertions(+), 34 deletions(-) diff --git a/src/data/composite/wiki-data/withReverseContributionList.js b/src/data/composite/wiki-data/withReverseContributionList.js index b7f7a95b..63e712bb 100644 --- a/src/data/composite/wiki-data/withReverseContributionList.js +++ b/src/data/composite/wiki-data/withReverseContributionList.js @@ -10,9 +10,11 @@ // is used, a fresh cache will always be created. import {input, templateCompositeFrom} from '#composite'; +import {stitchArrays} from '#sugar'; import {exitWithoutDependency, raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withFlattenedList, withMappedList} from '#composite/data'; import inputWikiData from './inputWikiData.js'; @@ -33,6 +35,8 @@ export default templateCompositeFrom({ outputs: ['#reverseContributionList'], steps: () => [ + // Common behavior -- + // Early exit with an empty array if the data list isn't available. exitWithoutDependency({ dependency: input('data'), @@ -46,45 +50,122 @@ export default templateCompositeFrom({ output: input.value({'#reverseContributionList': []}), }), + // Check for an existing cache record which corresponds to this + // input('list') and input('data'). If it exists, query it for the + // current thing, and raise that; if it doesn't, create it, put it + // where it needs to be, and provide it so the next steps can fill + // it in. { - dependencies: [input.myself(), input('data'), input('list')], + dependencies: [input('list'), input('data'), input.myself()], compute: (continuation, { - [input.myself()]: myself, - [input('data')]: data, [input('list')]: list, + [input('data')]: data, + [input.myself()]: myself, }) => { if (!caches.has(list)) { - caches.set(list, new WeakMap()); + const cache = new WeakMap(); + caches.set(list, cache); + + const cacheRecord = new WeakMap(); + cache.set(data, cacheRecord); + + return continuation({ + ['#cacheRecord']: cacheRecord, + }); } const cache = caches.get(list); if (!cache.has(data)) { const cacheRecord = new WeakMap(); + cache.set(data, cacheRecord); + + return continuation({ + ['#cacheRecord']: cacheRecord, + }); + } - for (const referencingThing of data) { - const contributionList = referencingThing[list]; + return continuation.raiseOutput({ + ['#reverseContributionList']: + cache.get(data).get(myself) ?? [], + }); + }, + }, - for (const contribution of contributionList) { - const {artist} = contribution; + // Unique behavior for contribution lists -- + + { + dependencies: [input('list')], + compute: (continuation, { + [input('list')]: list, + }) => continuation({ + ['#contributionListMap']: + thing => thing[list], + }), + }, - if (cacheRecord.has(artist)) { - cacheRecord.get(artist).push(contribution); + withMappedList({ + list: input('data'), + map: '#contributionListMap', + }).outputs({ + '#mappedList': '#contributionLists', + }), + + withFlattenedList({ + list: '#contributionLists', + }).outputs({ + '#flattenedList': '#referencingThings', + }), + + withMappedList({ + list: '#referencingThings', + map: input.value(contrib => [contrib.artist]), + }).outputs({ + '#mappedList': '#referencedThings', + }), + + // Common behavior -- + + // Actually fill in the cache record. Since we're building up a *reverse* + // reference list, track connections in terms of the referenced thing. + // No newly-provided dependencies here since we're mutating the cache + // record, which is properly in store and will probably be reused in the + // future (and certainly in the next step). + { + dependencies: ['#cacheRecord', '#referencingThings', '#referencedThings'], + compute: (continuation, { + ['#cacheRecord']: cacheRecord, + ['#referencingThings']: referencingThings, + ['#referencedThings']: referencedThings, + }) => { + stitchArrays({ + referencingThing: referencingThings, + referencedThings: referencedThings, + }).forEach(({referencingThing, referencedThings}) => { + for (const referencedThing of referencedThings) { + if (cacheRecord.has(referencedThing)) { + cacheRecord.get(referencedThing).push(referencingThing); } else { - cacheRecord.set(artist, [contribution]); + cacheRecord.set(referencedThing, [referencingThing]); } } - } + }); - cache.set(data, cacheRecord); - } - - return continuation({ - ['#reverseContributionList']: - cache.get(data).get(myself) ?? [], - }); + return continuation(); }, }, + + // Then just pluck out the current object from the now-filled cache record! + { + dependencies: ['#cacheRecord', input.myself()], + compute: (continuation, { + ['#cacheRecord']: cacheRecord, + [input.myself()]: myself, + }) => continuation({ + ['#reverseContributionList']: + cacheRecord.get(myself) ?? [], + }), + }, ], }); diff --git a/src/data/composite/wiki-data/withReverseReferenceList.js b/src/data/composite/wiki-data/withReverseReferenceList.js index 8cd540a5..1f8c082f 100644 --- a/src/data/composite/wiki-data/withReverseReferenceList.js +++ b/src/data/composite/wiki-data/withReverseReferenceList.js @@ -12,9 +12,11 @@ // so any changes should be reflected there (until these are combined). import {input, templateCompositeFrom} from '#composite'; +import {stitchArrays} from '#sugar'; import {exitWithoutDependency, raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withMappedList} from '#composite/data'; import inputWikiData from './inputWikiData.js'; @@ -35,6 +37,8 @@ export default templateCompositeFrom({ outputs: ['#reverseReferenceList'], steps: () => [ + // Common behavior -- + // Early exit with an empty array if the data list isn't available. exitWithoutDependency({ dependency: input('data'), @@ -48,42 +52,119 @@ export default templateCompositeFrom({ output: input.value({'#reverseReferenceList': []}), }), + // Check for an existing cache record which corresponds to this + // input('list') and input('data'). If it exists, query it for the + // current thing, and raise that; if it doesn't, create it, put it + // where it needs to be, and provide it so the next steps can fill + // it in. { - dependencies: [input.myself(), input('data'), input('list')], + dependencies: [input('list'), input('data'), input.myself()], compute: (continuation, { - [input.myself()]: myself, - [input('data')]: data, [input('list')]: list, + [input('data')]: data, + [input.myself()]: myself, }) => { if (!caches.has(list)) { - caches.set(list, new WeakMap()); + const cache = new WeakMap(); + caches.set(list, cache); + + const cacheRecord = new WeakMap(); + cache.set(data, cacheRecord); + + return continuation({ + ['#cacheRecord']: cacheRecord, + }); } const cache = caches.get(list); if (!cache.has(data)) { const cacheRecord = new WeakMap(); + cache.set(data, cacheRecord); + + return continuation({ + ['#cacheRecord']: cacheRecord, + }); + } + + return continuation.raiseOutput({ + ['#reverseReferenceList']: + cache.get(data).get(myself) ?? [], + }); + }, + }, + + // Unique behavior for reference lists -- + + { + dependencies: [input('list')], + compute: (continuation, { + [input('list')]: list, + }) => continuation({ + ['#referenceMap']: + thing => thing[list], + }), + }, + + withMappedList({ + list: input('data'), + map: '#referenceMap', + }).outputs({ + '#mappedList': '#referencedThings', + }), + + { + dependencies: [input('data')], + compute: (continuation, { + [input('data')]: data, + }) => continuation({ + ['#referencingThings']: + data, + }), + }, + + // Common behavior -- - for (const referencingThing of data) { - const referenceList = referencingThing[list]; - for (const referencedThing of referenceList) { + // Actually fill in the cache record. Since we're building up a *reverse* + // reference list, track connections in terms of the referenced thing. + // No newly-provided dependencies here since we're mutating the cache + // record, which is properly in store and will probably be reused in the + // future (and certainly in the next step). + { + dependencies: ['#cacheRecord', '#referencingThings', '#referencedThings'], + compute: (continuation, { + ['#cacheRecord']: cacheRecord, + ['#referencingThings']: referencingThings, + ['#referencedThings']: referencedThings, + }) => { + stitchArrays({ + referencingThing: referencingThings, + referencedThings: referencedThings, + }).forEach(({referencingThing, referencedThings}) => { + for (const referencedThing of referencedThings) { if (cacheRecord.has(referencedThing)) { cacheRecord.get(referencedThing).push(referencingThing); } else { cacheRecord.set(referencedThing, [referencingThing]); } } - } + }); - cache.set(data, cacheRecord); - } - - return continuation({ - ['#reverseReferenceList']: - cache.get(data).get(myself) ?? [], - }); + return continuation(); }, }, + + // Then just pluck out the current object from the now-filled cache record! + { + dependencies: ['#cacheRecord', input.myself()], + compute: (continuation, { + ['#cacheRecord']: cacheRecord, + [input.myself()]: myself, + }) => continuation({ + ['#reverseReferenceList']: + cacheRecord.get(myself) ?? [], + }), + }, ], }); -- cgit 1.3.0-6-gf8a5