After a good bit of research, it turns out I had stumbled into a well-understood issue (some call it a bug) known as “fold over.” In this post, I’ll go over what “fold over” means and a few alternatives for counteracting it, including a very useful design pattern known as an Immediately Invoked Function Expression (IIFE).
Background: A Game of Blackjack
score function would then accept the round as an argument, as the
hands parameter. Then a for loop held a closure (or function inside a function) that checked to see if
cardTot was over 21 and, if not, return the player’s blackjack score for that hand. Obviously, in blackjack, if your hand goes over 21, you bust and get 0 for the round.
1 2 3 4 5 6 7 8 9 10 11
The issue arose on line 11 when I tried to call
bjScore(); on Player1.
The error was that
cardTot was getting called on
hands, which did not exist. The zero-based
hands array was only 3 players long. How did this happen?
FYI – A simpler and probably better approach would have been simply to assign
bjScore for each player’s hand, instead of setting up the closure to do so later. But, as mentioned, I was just experimenting.
Understanding How Loops Can “Fold Over” Closures
A closure has access to the variables defined within its own scope, as well as the variables and even parameters of its outer function(s). However, a closure does not store the actual values of its outer functions. A closure only stores references to them.
Moreover a closure refers to its outer function’s variables even after the outer function has returned. In the context of for loops, this post-return access creates the “fold over” effect. Left at default access, closures will always call the last value of the iterator in the loop.
For example, in the
score(hands) example above, the for loop has fully executed before the
bjScore(); closure ever gets called. Thus, the value of i on line 10 is hands.length or 2. And when
bjScore(); does get called on the next line, doing so actually increments i one more time, so that that a
TypeError is raised.
Even if calling
bjScore(); did not increment
i, the value of
i at the end of the for loop would still be 2, which would prevent us from retrieving any of the actual blackjack scores for players other than Player3, who’s at the 2-index. In other words, we would get the final player’s
bjScore no matter which player we called
Immediately Invoked Function Expression (IIFE) to the Rescue
One way to counteract “fold over” in loops is to use an IIFE, which is a design pattern that saves the state of a variable value so that it can be passed into a closure. Usually a closure would have to accept post-return access to a loop’s final value, but with IIFE, we can “lock in” and pass the value into the closure at each turn of the loop.
1 2 3 4 5 6 7 8 9 10 11
Here is the
Note also that we don’t need to call
bjScore(); as a function any longer since the IIFE created
bjScore as a property on each player object. Now we can just call
bjScore and get back the correct value for each player.