// B is the type of object we handle. sclass TransformersOnObjects implements Steppable { // using the oldschool type F1 here because it's a bit easier to handle new AllOnAll allOnAll; new Set bs; new MultiSetMap trails; srecord TransformationTrail(O transformer, O argument) {} // If this is true and a transformer returns an Iterable, // we unpack it and discard the Iterable. bool autoUnpackIterables = true; // make a trail for each transformer application bool autoTrails = true; // keep multiple trails per object bool allowMultipleTrails = true; AllOnAllOperation op = new(allOnAll, (a, b) -> { if (!callableOn_nonSynthetic(a, "get", b)) ret; O out = callFOpt(a, b); O trail = autoTrails ? makeTrailForTransformerApplication(a, b) : null; if (autoUnpackIterables && out instanceof Iterable) for (O x : (Iterable) out) addObject((B) x, trail); else addObject((B) out, trail); }); bool add(B b) { ret addObject(b); } bool addObject(B b) { if (!addIfNotNull(bs, b)) false; allOnAll.addB(b); true; } void addObject(B b, O trail) { if (b == null) ret; add(b); if (trail != null && (allowMultipleTrails || !trails.containsKey(b))) trails.put(b, trail); } void addTransformer(F1 f) { allOnAll.addA(f); } void addTransformer(F2 f) { addTransformer(func(O a) { addTransformer(func(O b) { callableOn_nonSynthetic(f, "get", a, b) ? f.get(a, b) : null }); null; }); } public bool step() { ret op.step(); } L getObjects() { ret allOnAll.getBs(); } TransformationTrail makeTrailForTransformerApplication(O transformer, B argument) { ret new TransformationTrail(transformer, argument); } void printWithTrails() { pnlMap(getObjects(), b -> { Set _trails = trails.get(b); ret empty(_trails) ? str(b) : b + " [" + nTrails(_trails) + ": " + joinWithComma(_trails) + "]"; }); } }