Skip to content

Instantly share code, notes, and snippets.

@dougpagani
Last active April 12, 2021 13:25
Show Gist options
  • Save dougpagani/a3e066494d8036cc8b7c51b103c58a25 to your computer and use it in GitHub Desktop.
Save dougpagani/a3e066494d8036cc8b7c51b103c58a25 to your computer and use it in GitHub Desktop.
MEAT & POTATOES: Trying out an idea of using closure-leveraging nested snippets for enhancing readability (name == hoisted helpers?)
// does not work because-hoisting.js
function calculateWinnerRecursion(squares, possibleWinConditions) {
if (noMorePossibleWinConditions()) { return null }
if (winConditionIsMet()) { return theWinner() }
return SELF(squares, possibleWinConditions.shift()))
// HOISTED HELPERS
let SELF = calculateWinnerRecursion
let theWinner = () => squares[possibleWinConditions[0][0]]
let noMorePossibleWinConditions = () => possibleWinConditions.length === 0
let winConditionIsMet = () => {
squares[possibleWinConditions[0][0]]
&& squares[possibleWinConditions[0][0]] === squares[possibleWinConditions[0][1]]
&& squares[possibleWinConditions[0][0]] === squares[possibleWinConditions[0][2]]
}
}
function calculateWinner(squares) {
const WIN_CONDITIONS = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
return calculateWinnerRecursion (squares, WIN_CONDITIONS)
}
function calculateWinnerRecursion(squares, lines) {
if (lines.length === 0) {
return null
}
if (squares[lines[0][0]] && squares[lines[0][0]] === squares[lines[0][1]] && squares[lines[0][0]] === squares[lines[0][2]]) {
return squares[lines[0][0]];
}
else {
return calculateWinnerRecursion(squares, lines.shift())
}
}
function calculateWinner(squares) {
const LINES = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
return calculateWinnerRecursion (squares, LINES)
}
function calculateWinnerRecursion(squares, possibleWinConditions) {
let RECURSION = calculateWinnerRecursion
let thisWinCondition = possibleWinConditions[0]
if (noMorePossibleWinConditions()) { return null }
if (winConditionIsMet()) { return theWinner() }
return RECURSION(squares, possibleWinConditions.slice(1))
// hoisted readability helpers -- need to be all thunks w/o main wrapper
function noMorePossibleWinConditions(){return (possibleWinConditions.length === 0)}
function theWinner(){return squares[thisWinCondition[0]]}
function winConditionIsMet(){return (
squares[thisWinCondition[0]]
&& squares[thisWinCondition[0]] === squares[thisWinCondition[1]]
&& squares[thisWinCondition[0]] === squares[thisWinCondition[2]]
)}
}
function calculateWinnerRecursion(squares, possibleWinConditions) {
function MAIN() { // MEAT()
if (noMorePossibleWinConditions()) { return null }
if (winConditionIsMet()) { return theWinner() }
return RECURSION(squares, possibleWinConditions.slice(1))
}
// HELPERS -- no longer need to all be thunks
const noMorePossibleWinConditions = possibleWinConditions.length === 0
const theWinner = squares[thisWinCondition[0]]
const winConditionIsMet = (
squares[thisWinCondition[0]]
&& squares[thisWinCondition[0]] === squares[thisWinCondition[1]]
&& squares[thisWinCondition[0]] === squares[thisWinCondition[2]]
)
const RECURSION = calculateWinnerRecursion
const thisWinCondition = possibleWinConditions[0]
return MAIN() // MEAT()
}
function calculateWinnerRecursion(squares, possibleWinConditions) {
function MEAT() {
if (noMorePossibleWinConditions()) { return null }
if (winConditionIsMet()) { return theWinner() }
return RECURSION(squares, possibleWinConditions.slice(1))
}
// POTATOES -- all hunks so post-base-case-code doesn't get executed
const noMorePossibleWinConditions = possibleWinConditions.length === 0
const theWinner = squares[thisWinCondition[0]]
const winConditionIsMet = (
squares[thisWinCondition[0]]
&& squares[thisWinCondition[0]] === squares[thisWinCondition[1]]
&& squares[thisWinCondition[0]] === squares[thisWinCondition[2]]
)
const RECURSION = calculateWinnerRecursion
const thisWinCondition = possibleWinConditions[0]
return MEAT()
}
// We need a double-nester because we need the enclosing
// ... scope for the dynamically-changing, UNREFERENCED possibleWinConditions.
// We could get away without this, but it would hamper readability,
// ... which is the core purpose of the thunks.
function calculateWinner(squares) {
// could be dynamically-generated win conditions (1:row,2:column,3:diagonal)
const WIN_CONDITIONS = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
return TRY(WIN_CONDITIONS)
function TRY(possibleWinConditions) {
function meat(){
if (noMorePossibleWinConditions()) {return null}
if (thisWinConditionIsMet()) {return theWinner()}
else {return RECUR()}
}
// HELPER THUNKS
const RECUR = () => TRY(possibleWinConditions.slice(1))
const noMorePossibleWinConditions = () => possibleWinConditions.length === 0
const thisWinConditionIsMet = () => (
// could iterate from 0+1->n(thisWinCondition)
squareValueOfThisWinConditionPosition(0)
&& squareValueOfThisWinConditionPosition(0) === squareValueOfThisWinConditionPosition(1)
&& squareValueOfThisWinConditionPosition(0) === squareValueOfThisWinConditionPosition(2)
)
const thisWinCondition = () => possibleWinConditions[0]
const theWinner = () => squareValueOfThisWinConditionPosition(0)
const squareValueOfThisWinConditionPosition = (i) => squares[thisWinCondition()[i]]
return meat()
}
}
"Functions cooked to perfection" -- Thunks + closures is a beautiful thing.
Rules:
- small (otherwise you lose context that you're even in a nested function b/c you dont notice indentation)
- no side effects (especially communicating b/t different nested functions)
- no accumulators
- only for readability
- the function is written first without them
- their purpose is to access, as per the closure, variables
- good use cases: error reporters, complex conditionals, long-winded getters
- dont take in variables -- just use the closure
Benefits:
- readability
- guaranteed to move together (cohesive/tight-coupling)
- UNTESTABLE for things which shouldn't be tested
- awesome stack traces (named logical bits)
We use hunks, like MEAT(), to defer execution until later.
Nothing in here (the bits) needs to be deferred. But say, this were a http-request handler... you'd want your reportUnexpectedError() logger function + its noisy preamble where you structure the message/data payload, named & deferred for when it actually is hit.
It's all about readability of the meat. And, you want to highlight & lift to the top.
Actually there is one that needs to be deferred -- the recursive call.
IMPORTANT NOTE: to be safe, it's better to defer everything. That way nothing unexpected happen. For example, if you don't defer a dangerous case (e.g. indexing an empty array), as you normally would with the potatoes mixed-in directly, you may hit an otherwise-impossible calculation. Or, simply spoken, this is the most efficient way to do it since you shouldn't be executing predicated code anyways.
// does not work because-hoisting.js
function calculateWinnerRecursion(squares, possibleWinConditions) {
// base case -- empty array means no more possible ways to win, game goes on
if (possibleWinConditions.length === 0) {
return null
}
// Win condition is met
if (
squares[possibleWinConditions[0][0]]
&& squares[possibleWinConditions[0][0]] === squares[possibleWinConditions[0][1]]
&& squares[possibleWinConditions[0][0]] === squares[possibleWinConditions[0][2]]
) {
return squares[possibleWinConditions[0][0]]
}
return calculateWinnerRecursion(squares, possibleWinConditions.shift()))
}
function calculateWinner(squares) {
const WIN_CONDITIONS = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
return calculateWinnerRecursion (squares, WIN_CONDITIONS)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment