Skip to content

Commit a46db43

Browse files
committed
data: withReverseList_template
1 parent 6285a05 commit a46db43

File tree

3 files changed

+203
-325
lines changed

3 files changed

+203
-325
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// Baseline implementation shared by or underlying reverse lists.
2+
//
3+
// This is a very rudimentary "these compositions have basically the same
4+
// shape but slightly different guts midway through" kind of solution,
5+
// and should use compositional subroutines instead, once those are ready.
6+
//
7+
// But, until then, this has the same effect of avoiding code duplication
8+
// and clearly identifying differences.
9+
//
10+
// ---
11+
//
12+
// This implementation uses a global cache (via WeakMap) to attempt to speed
13+
// up subsequent similar accesses.
14+
//
15+
// This has absolutely not been rigorously tested with altering properties of
16+
// data objects in a wiki data array which is reused. If a new wiki data array
17+
// is used, a fresh cache will always be created.
18+
//
19+
20+
import {input, templateCompositeFrom} from '#composite';
21+
import {sortByDate} from '#sort';
22+
import {stitchArrays} from '#sugar';
23+
24+
import {exitWithoutDependency, raiseOutputWithoutDependency}
25+
from '#composite/control-flow';
26+
import {withFlattenedList, withMappedList} from '#composite/data';
27+
28+
import inputWikiData from '../inputWikiData.js';
29+
30+
export default function withReverseList_template({
31+
annotation,
32+
33+
propertyInputName,
34+
outputName,
35+
36+
customCompositionSteps,
37+
}) {
38+
// Mapping of reference list property to WeakMap.
39+
// Each WeakMap maps a wiki data array to another weak map,
40+
// which in turn maps each referenced thing to an array of
41+
// things referencing it.
42+
const caches = new Map();
43+
44+
return templateCompositeFrom({
45+
annotation,
46+
47+
inputs: {
48+
data: inputWikiData({
49+
allowMixedTypes: false,
50+
}),
51+
52+
[propertyInputName]: input({
53+
type: 'string',
54+
}),
55+
},
56+
57+
outputs: [outputName],
58+
59+
steps: () => [
60+
// Early exit with an empty array if the data list isn't available.
61+
exitWithoutDependency({
62+
dependency: input('data'),
63+
value: input.value([]),
64+
}),
65+
66+
// Raise an empty array (don't early exit) if the data list is empty.
67+
raiseOutputWithoutDependency({
68+
dependency: input('data'),
69+
mode: input.value('empty'),
70+
output: input.value({[outputName]: []}),
71+
}),
72+
73+
// Check for an existing cache record which corresponds to this
74+
// property input and input('data'). If it exists, query it for the
75+
// current thing, and raise that; if it doesn't, create it, put it
76+
// where it needs to be, and provide it so the next steps can fill
77+
// it in.
78+
{
79+
dependencies: [input(propertyInputName), input('data'), input.myself()],
80+
81+
compute: (continuation, {
82+
[input(propertyInputName)]: property,
83+
[input('data')]: data,
84+
[input.myself()]: myself,
85+
}) => {
86+
if (!caches.has(property)) {
87+
const cache = new WeakMap();
88+
caches.set(property, cache);
89+
90+
const cacheRecord = new WeakMap();
91+
cache.set(data, cacheRecord);
92+
93+
return continuation({
94+
['#cacheRecord']: cacheRecord,
95+
});
96+
}
97+
98+
const cache = caches.get(property);
99+
100+
if (!cache.has(data)) {
101+
const cacheRecord = new WeakMap();
102+
cache.set(data, cacheRecord);
103+
104+
return continuation({
105+
['#cacheRecord']: cacheRecord,
106+
});
107+
}
108+
109+
return continuation.raiseOutput({
110+
[outputName]:
111+
cache.get(data).get(myself) ?? [],
112+
});
113+
},
114+
},
115+
116+
...customCompositionSteps(),
117+
118+
// Actually fill in the cache record. Since we're building up a *reverse*
119+
// reference list, track connections in terms of the referenced thing.
120+
// Although we gather all referenced things into a set and provide that
121+
// for sorting purposes in the next step, we *don't* reprovide the cache
122+
// record, because we're mutating that in-place - we'll just reuse its
123+
// existing '#cacheRecord' dependency.
124+
{
125+
dependencies: ['#cacheRecord', '#referencingThings', '#referencedThings'],
126+
compute: (continuation, {
127+
['#cacheRecord']: cacheRecord,
128+
['#referencingThings']: referencingThings,
129+
['#referencedThings']: referencedThings,
130+
}) => {
131+
const allReferencedThings = new Set();
132+
133+
stitchArrays({
134+
referencingThing: referencingThings,
135+
referencedThings: referencedThings,
136+
}).forEach(({referencingThing, referencedThings}) => {
137+
for (const referencedThing of referencedThings) {
138+
if (cacheRecord.has(referencedThing)) {
139+
cacheRecord.get(referencedThing).push(referencingThing);
140+
} else {
141+
cacheRecord.set(referencedThing, [referencingThing]);
142+
allReferencedThings.add(referencedThing);
143+
}
144+
}
145+
});
146+
147+
return continuation({
148+
['#allReferencedThings']:
149+
allReferencedThings,
150+
});
151+
},
152+
},
153+
154+
// Sort the entries in the cache records, too, just by date - the rest of
155+
// sorting should be handled outside of this composition, either preceding
156+
// (changing the 'data' input) or following (sorting the output).
157+
// Again we're mutating in place, so no need to reprovide '#cacheRecord'
158+
// here.
159+
{
160+
dependencies: ['#cacheRecord', '#allReferencedThings'],
161+
compute: (continuation, {
162+
['#cacheRecord']: cacheRecord,
163+
['#allReferencedThings']: allReferencedThings,
164+
}) => {
165+
for (const referencedThing of allReferencedThings) {
166+
if (cacheRecord.has(referencedThing)) {
167+
const referencingThings = cacheRecord.get(referencedThing);
168+
sortByDate(referencingThings);
169+
}
170+
}
171+
172+
return continuation();
173+
},
174+
},
175+
176+
// Then just pluck out the current object from the now-filled cache record!
177+
{
178+
dependencies: ['#cacheRecord', input.myself()],
179+
compute: (continuation, {
180+
['#cacheRecord']: cacheRecord,
181+
[input.myself()]: myself,
182+
}) => continuation({
183+
[outputName]:
184+
cacheRecord.get(myself) ?? [],
185+
}),
186+
},
187+
],
188+
});
189+
}
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,18 @@
11
// Analogous implementation for withReverseReferenceList, for contributions.
2-
// This is mostly duplicate code and both should be ported to the same
3-
// underlying data form later on.
4-
//
5-
// This implementation uses a global cache (via WeakMap) to attempt to speed
6-
// up subsequent similar accesses.
7-
//
8-
// This has absolutely not been rigorously tested with altering properties of
9-
// data objects in a wiki data array which is reused. If a new wiki data array
10-
// is used, a fresh cache will always be created.
112

12-
import {input, templateCompositeFrom} from '#composite';
13-
import {sortByDate} from '#sort';
14-
import {stitchArrays} from '#sugar';
3+
import withReverseList_template from './helpers/withReverseList-template.js';
154

16-
import {exitWithoutDependency, raiseOutputWithoutDependency}
17-
from '#composite/control-flow';
18-
import {withFlattenedList, withMappedList} from '#composite/data';
19-
20-
import inputWikiData from './inputWikiData.js';
5+
import {input} from '#composite';
216

22-
// Mapping of reference list property to WeakMap.
23-
// Each WeakMap maps a wiki data array to another weak map,
24-
// which in turn maps each referenced thing to an array of
25-
// things referencing it.
26-
const caches = new Map();
7+
import {withFlattenedList, withMappedList} from '#composite/data';
278

28-
export default templateCompositeFrom({
9+
export default withReverseList_template({
2910
annotation: `withReverseContributionList`,
3011

31-
inputs: {
32-
data: inputWikiData({allowMixedTypes: false}),
33-
list: input({type: 'string'}),
34-
},
35-
36-
outputs: ['#reverseContributionList'],
37-
38-
steps: () => [
39-
// Common behavior --
40-
41-
// Early exit with an empty array if the data list isn't available.
42-
exitWithoutDependency({
43-
dependency: input('data'),
44-
value: input.value([]),
45-
}),
46-
47-
// Raise an empty array (don't early exit) if the data list is empty.
48-
raiseOutputWithoutDependency({
49-
dependency: input('data'),
50-
mode: input.value('empty'),
51-
output: input.value({'#reverseContributionList': []}),
52-
}),
53-
54-
// Check for an existing cache record which corresponds to this
55-
// input('list') and input('data'). If it exists, query it for the
56-
// current thing, and raise that; if it doesn't, create it, put it
57-
// where it needs to be, and provide it so the next steps can fill
58-
// it in.
59-
{
60-
dependencies: [input('list'), input('data'), input.myself()],
61-
62-
compute: (continuation, {
63-
[input('list')]: list,
64-
[input('data')]: data,
65-
[input.myself()]: myself,
66-
}) => {
67-
if (!caches.has(list)) {
68-
const cache = new WeakMap();
69-
caches.set(list, cache);
70-
71-
const cacheRecord = new WeakMap();
72-
cache.set(data, cacheRecord);
73-
74-
return continuation({
75-
['#cacheRecord']: cacheRecord,
76-
});
77-
}
78-
79-
const cache = caches.get(list);
80-
81-
if (!cache.has(data)) {
82-
const cacheRecord = new WeakMap();
83-
cache.set(data, cacheRecord);
84-
85-
return continuation({
86-
['#cacheRecord']: cacheRecord,
87-
});
88-
}
89-
90-
return continuation.raiseOutput({
91-
['#reverseContributionList']:
92-
cache.get(data).get(myself) ?? [],
93-
});
94-
},
95-
},
96-
97-
// Unique behavior for contribution lists --
12+
propertyInputName: 'list',
13+
outputName: '#reverseContributionList',
9814

15+
customCompositionSteps: () => [
9916
{
10017
dependencies: [input('list')],
10118
compute: (continuation, {
@@ -125,77 +42,5 @@ export default templateCompositeFrom({
12542
}).outputs({
12643
'#mappedList': '#referencedThings',
12744
}),
128-
129-
// Common behavior --
130-
131-
// Actually fill in the cache record. Since we're building up a *reverse*
132-
// reference list, track connections in terms of the referenced thing.
133-
// Although we gather all referenced things into a set and provide that
134-
// for sorting purposes in the next step, we *don't* reprovide the cache
135-
// record, because we're mutating that in-place - we'll just reuse its
136-
// existing '#cacheRecord' dependency.
137-
{
138-
dependencies: ['#cacheRecord', '#referencingThings', '#referencedThings'],
139-
compute: (continuation, {
140-
['#cacheRecord']: cacheRecord,
141-
['#referencingThings']: referencingThings,
142-
['#referencedThings']: referencedThings,
143-
}) => {
144-
const allReferencedThings = new Set();
145-
146-
stitchArrays({
147-
referencingThing: referencingThings,
148-
referencedThings: referencedThings,
149-
}).forEach(({referencingThing, referencedThings}) => {
150-
for (const referencedThing of referencedThings) {
151-
if (cacheRecord.has(referencedThing)) {
152-
cacheRecord.get(referencedThing).push(referencingThing);
153-
} else {
154-
cacheRecord.set(referencedThing, [referencingThing]);
155-
allReferencedThings.add(referencedThing);
156-
}
157-
}
158-
});
159-
160-
return continuation({
161-
['#allReferencedThings']:
162-
allReferencedThings,
163-
});
164-
},
165-
},
166-
167-
// Sort the entries in the cache records, too, just by date - the rest of
168-
// sorting should be handled outside of withReverseContributionList, either
169-
// preceding (changing the 'data' input) or following (sorting the output).
170-
// Again we're mutating in place, so no need to reprovide '#cacheRecord'
171-
// here.
172-
{
173-
dependencies: ['#cacheRecord', '#allReferencedThings'],
174-
compute: (continuation, {
175-
['#cacheRecord']: cacheRecord,
176-
['#allReferencedThings']: allReferencedThings,
177-
}) => {
178-
for (const referencedThing of allReferencedThings) {
179-
if (cacheRecord.has(referencedThing)) {
180-
const referencingThings = cacheRecord.get(referencedThing);
181-
sortByDate(referencingThings);
182-
}
183-
}
184-
185-
return continuation();
186-
},
187-
},
188-
189-
// Then just pluck out the current object from the now-filled cache record!
190-
{
191-
dependencies: ['#cacheRecord', input.myself()],
192-
compute: (continuation, {
193-
['#cacheRecord']: cacheRecord,
194-
[input.myself()]: myself,
195-
}) => continuation({
196-
['#reverseContributionList']:
197-
cacheRecord.get(myself) ?? [],
198-
}),
199-
},
20045
],
20146
});

0 commit comments

Comments
 (0)