Skip to content

Instantly share code, notes, and snippets.

@jorendorff
Last active October 7, 2023 23:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jorendorff/4ec9fe31493e1fb24a7e to your computer and use it in GitHub Desktop.
Save jorendorff/4ec9fe31493e1fb24a7e to your computer and use it in GitHub Desktop.
// *** Before *****************************************************************
// The program operated on a large document tree (DOM-like, but not exactly DOM).
// It had several functions that needed to walk the entire tree,
// which was done with recursion, like this:
function stripGrammar(doc) {
...
function visit(parent) {
for (var [i, child] of parent.content.entries()) {
if (typeof child === "object") {
if (isGrammarBlock(child, false)) {
stripGrammarBlock(parent, i);
} else if (isNonterminal(child)) {
stripGrammarInline(parent, i);
}
visit(child);
}
}
}
visit(doc);
}
// *** After ******************************************************************
// The code is split into two parts: stripGrammar and a new generator-function
// called allDescendants.
/*
* Find all elements that are descendants of 'parent'. For each one,
* yield the array [parent, index, child]
* such that parent.content[index] === child.
*/
function* allDescendants(parent) {
for (var [index, child] in parent.content.entries()) {
if (typeof child === "object") {
yield [parent, index, child];
yield* allDescendants(child);
}
}
}
function stripGrammar(doc) {
...
for (var [parent, i, child] of allDescendants(doc)) {
if (isGrammarBlock(child, False)) {
stripGrammarBlock(parent, i);
} else if (isNonterminal(child)) {
stripGrammarInline(parent, i);
}
}
}
// I think this refactoring is good because:
// (a) what the code is trying to do to each element in the document is nontrivial
// (b) finding all the elements in the document is nontrivial
// (c) before refactoring, these things were kind of mashed together
// and it wasn't clear which was which.
//
// Also, in the real program, it turns out the same generator could be used in
// several places.
//
// Without generators, the best way accomplish a similar refactoring is to make
// allDescendants a higher-order function that takes a callback. There's really
// nothing wrong with that unless any caller needs to break or early-return out
// of the loop; or combine the sequence of elements with some other sequence,
// in the style of Python's zip(); or treat the elements as a data set for further
// querying and operations, in the style of jQuery element sets.
//
// Because generators are iterators, and iterators are very flexible, the
// caller gets to decide how to consume the data.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment