An Excursion to Fold and Back

31/01/2024

When I was originally coding the JavaScript for the front page of this website, I had a problem with the UFO. I didn't need to specify coordinates for the navigation buttons because I was adding them to a centering container, but I still needed their x-coordinates to draw a UFO on the canvas at the location of each button. At the time (for some reason) I couldn't figure out how to get the button coordinates so I eyeballed it and hardcoded them in. This became frustrating over time because every time I thought I'd finalized the nav button text, I ended up changing it again, and had to manually adjust the coordinates until everything lined up.

To minimize that work, I had the idea to just eyeball / hardcode the first coordinate, then base the rest of the coordinates on that plus some button length(s). So i wrote a function that created a button on the page and returned the next x-coordinate, based on the previous x-coordinate and the button width:

function placeAndGiveNextX(button, xCoord){ const width = getWidth(button) button.addEventListener('mouseenter', () => drawUfo(xCoord, width, 75)) return xCoord + width + buttonPadding }

I chained the function calls together like:

placeAndGiveNextX(rightBtn, placeAndGiveNextX(middleBtn, placeAndGiveNextX(leftBtn, leftmostX)))

Which was ok, but I was like, gee, wouldn't it be great if I could add like, a hundred buttons without having to write a ridiculously long line of code? Something must have stuck in my brain from my brief foray into Scheme because I suddenly recognized the line above as something you can abstract with a recursive higher-order function called Fold.

I was actually very excited to be able to use fold, so I wrote the function. It seems like it turned out kind of backwards because instead of breaking the array into a one-element head and a long tail, I broke it into a long head and a one-element tail. If you put in the values from above (arr = [leftBtn, middleBtn, rightBtn], f = placeAndGiveNextX, initVal = leftmostX) and expand it out, fold returns that same line of chained functions! However, what I created here also isn't tail recursive, so I might come back to this later and try & make a reverse version.

function fold(arr, f, initVal){ if (arr.length>1) { const front = arr.slice(0, arr.length-1) const last = arr[arr.length-1] return f(last, fold(front, f, initVal)) } else{ return f(arr[0], initVal) } }

After I did all that, I looked it up and found out that JavaScript already has a built-in method for fold called reduce(), so I decided to use that instead:

btnsArr.reduce((acc, currVal) => placeAndGiveNextX(currVal, acc), leftmostX)

Finally, after allllll that, I realized that I could get the x-coordinates of each button after all, so I wrote a simple forEach loop, and left it at that. Phew! What a convoluted way to come to a solution :)

btnsArr.forEach(btn => { btn.addEventListener('mouseenter', () => drawUfo(getX(btn), getWidth(btn), 75)) btn.addEventListener('mouseleave', drawMountains) })
🠜⭪ HOME 🠜⭪ SITEMAP