Closure (PART 3)

In this third and final exercise on closure, we’re going to implement a basic calculator. The calculator() function will produce an instance of a calculator that maintains its own state, in the form of a function (calc(..), below):

  1. function calculator() {
  2. // ..
  3. }
  4. var calc = calculator();

Each time calc(..) is called, you’ll pass in a single character that represents a keypress of a calculator button. To keep things more straightforward, we’ll restrict our calculator to supporting entering only digits (0-9), arithmetic operations (+, -, *, /), and “=” to compute the operation. Operations are processed strictly in the order entered; there’s no “( )” grouping or operator precedence.

We don’t support entering decimals, but the divide operation can result in them. We don’t support entering negative numbers, but the “-“ operation can result in them. So, you should be able to produce any negative or decimal number by first entering an operation to compute it. You can then keep computing with that value.

The return of calc(..) calls should mimic what would be shown on a real calculator, like reflecting what was just pressed, or computing the total when pressing “=”.

For example:

  1. calc("4"); // 4
  2. calc("+"); // +
  3. calc("7"); // 7
  4. calc("3"); // 3
  5. calc("-"); // -
  6. calc("2"); // 2
  7. calc("="); // 75
  8. calc("*"); // *
  9. calc("4"); // 4
  10. calc("="); // 300
  11. calc("5"); // 5
  12. calc("-"); // -
  13. calc("5"); // 5
  14. calc("="); // 0

Since this usage is a bit clumsy, here’s a useCalc(..) helper, that runs the calculator with characters one at a time from a string, and computes the display each time:

  1. function useCalc(calc,keys) {
  2. return [...keys].reduce(
  3. function showDisplay(display,key){
  4. var ret = String( calc(key) );
  5. return (
  6. display +
  7. (
  8. (ret != "" && key == "=") ?
  9. "=" :
  10. ""
  11. ) +
  12. ret
  13. );
  14. },
  15. ""
  16. );
  17. }
  18. useCalc(calc,"4+3="); // 4+3=7
  19. useCalc(calc,"+9="); // +9=16
  20. useCalc(calc,"*8="); // *5=128
  21. useCalc(calc,"7*2*3="); // 7*2*3=42
  22. useCalc(calc,"1/0="); // 1/0=ERR
  23. useCalc(calc,"+3="); // +3=ERR
  24. useCalc(calc,"51="); // 51

The most sensible usage of this useCalc(..) helper is to always have “=” be the last character entered.

Some of the formatting of the totals displayed by the calculator require special handling. I’m providing this formatTotal(..) function, which your calculator should use whenever it’s going to return a current computed total (after an "=" is entered):

  1. function formatTotal(display) {
  2. if (Number.isFinite(display)) {
  3. // constrain display to max 11 chars
  4. let maxDigits = 11;
  5. // reserve space for "e+" notation?
  6. if (Math.abs(display) > 99999999999) {
  7. maxDigits -= 6;
  8. }
  9. // reserve space for "-"?
  10. if (display < 0) {
  11. maxDigits--;
  12. }
  13. // whole number?
  14. if (Number.isInteger(display)) {
  15. display = display
  16. .toPrecision(maxDigits)
  17. .replace(/\.0+$/,"");
  18. }
  19. // decimal
  20. else {
  21. // reserve space for "."
  22. maxDigits--;
  23. // reserve space for leading "0"?
  24. if (
  25. Math.abs(display) >= 0 &&
  26. Math.abs(display) < 1
  27. ) {
  28. maxDigits--;
  29. }
  30. display = display
  31. .toPrecision(maxDigits)
  32. .replace(/0+$/,"");
  33. }
  34. }
  35. else {
  36. display = "ERR";
  37. }
  38. return display;
  39. }

Don’t worry too much about how formatTotal(..) works. Most of its logic is a bunch of handling to limit the calculator display to 11 characters max, even if negatives, repeating decimals, or even “e+” exponential notation is required.

Again, don’t get too mired in the mud around calculator-specific behavior. Focus on the memory of closure.

Try the exercise for yourself, then check out the suggested solution at the end of this appendix.