Last active
October 7, 2023 23:23
-
-
Save jorendorff/4ec9fe31493e1fb24a7e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// *** 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