« get me outta code hell

data: withReverse{Reference,Contribution}List: factor commonality - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2024-06-02 22:24:16 -0300
committer(quasar) nebula <qznebula@protonmail.com>2024-06-18 22:56:07 -0300
commitcea70e41a53b815b224aacf838a6aaa165c30f03 (patch)
treee52d1edc811da61639fd1750e6d875cb0eac8b87 /src
parent163a5aabc963e1817020048b94d7d215ad225a77 (diff)
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!
Diffstat (limited to 'src')
-rw-r--r--src/data/composite/wiki-data/withReverseContributionList.js119
-rw-r--r--src/data/composite/wiki-data/withReverseReferenceList.js111
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) ?? [],
+      }),
+    },
   ],
 });