1

I am currently following James Moore's Functional programming for beginners with JavaScript course on Udemy. I am having a little trouble understanding how a piece of code works:

const grades = [22, 77, 89, 90, 45, 77, 87, 92, 66, 44, 74, 81, 82, 81, 97];

const gradeCount = grades.reduce(computer, {});

function computer(acc, grade){
  const {a = 0, b = 0, c = 0, d = 0, f = 0} = acc;
  if (grade >= 90){
      return {...acc, a: a + 1};
  } else if (grade >= 80){
      return {...acc, b: b +1};
  } else if (grade >= 70){
      return {...acc, c: c + 1};
  }else if (grade >= 60){
      return {...acc, d: d + 1};
  } else { 
      return {...acc, f: f + 1};
  }
}

console.log(gradeCount);

  1. My first question is; why is destructuring used here as opposed to const a=0, b=0, c=0, d=0, f=0;? This seems far less verbose when compared to the original technique?

  2. Secondly, why is it that the reduce method returns one object containing all the grades with their corresponding quantities as opposed to a separate object for each grade?

Thanks in advance for any advice.

Louis
  • 45
  • 4
  • 2
    `const a=0, b=0, c=0, d=0, f=0;` does not initialise the variables to the property values of `acc`, like the destructuring does. – Bergi Apr 08 '20 at 12:58
  • Notice that this is not the cleanest code anyway. It would have been better to write two separate functions, called like `counts(gradePoints.map(pointsToGrade))` – Bergi Apr 08 '20 at 13:57

2 Answers2

1

My first question is; why is destructuring used here as opposed to const a=0, b=0, c=0, d=0, f=0;? This seems far less verbose when compared to the original technique?

So the usual way of assigning variables is something like this.

const obj = {
  a: 'A',
  b: 'B',
  c: 'C',
  d: 'D',
  e: 'E'
}

let a = obj.a || 0,
  b = obj.b || 0,
  c = obj.c || 0,
  d = obj.d || 0,
  e = obj.e || 0;

Did you see how much code this is? Let's achieve the same using destructuring.

let {a = 0, b = 0, c = 0, d = 0, e = 0} = obj;

This is so much better and does the same thing. It is defining the variables from a to e and setting the default value to 0.

If you would have to get nested values from the object, then destructuring makes it much easier.

const user = {
  name: {
    firstName: 'John',
    lastName: 'Doe'
  },
  age: '26'
}

let {name: {firstName, lastName}, age} = user;

Lets see how we can use destructuring in the function parameters. So we have a user object which returns fullName of the user. If firstName or lastName is empty then we have to get the default name.

This is how we normally do:

function getUserFullName(user) {
  let firstName = user.firstName || 'Jane';
  let lastName = user.lastName || 'Doe';

  return firstName + ' ' + lastName;
}


const user = {
  name: {
    firstName: 'John',
    lastName: 'Doe'
  },
  age: '26'
}

getUserFullName(user);

Same thing using destructuring can be done like this:

function getUserFullName({name: {firstName = 'Jane', lastName = 'Doe'}} = user) {
  return `${firstName} ${lastName}`;
}

const user = {
  name: {
    firstName: 'John',
    lastName: 'Doe'
  },
  age: '26'
}

getUserFullName(user);

It might be confusing at first, but you will realize how handy this comes, once you start using it.

Secondly, why is it that the reduce method returns one object containing all the grades with their corresponding quantities as opposed to a separate object for each grade?

This is because the computer method returns an object that contains all the grades.

return {...acc, a: a + 1};

To know why you need to know how the reduce method works. From the MDN docs:

The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value

So every time the computer method is called with the acc and the grade and returns a single value at the end.

SpiritOfDragon
  • 1,209
  • 2
  • 12
  • 25
1

My first question is; why is destructuring used here as opposed to const a=0, b=0, c=0, d=0, f=0;? This seems far less verbose when compared to the original technique?

If you declare the variables as you suggest, you will not get the previous values from the object:

function original(obj) {
  const { a=0, b=0, c=0, d=0, f=0 } = obj;
  console.log(`const { a=0, b=0, c=0, d=0, f=0 } = obj;
a = ${a}
b = ${b}
c = ${c}
d = ${d}
f = ${f}
`);
}

function proposed(obj) {
  const a=0, b=0, c=0, d=0, f=0;
  
  console.log(`const a=0, b=0, c=0, d=0, f=0;
a = ${a}
b = ${b}
c = ${c}
d = ${d}
f = ${f}
`);
}

const obj = { a: 1, b: 2, d: 4};

original(obj);
proposed(obj);

Destructuring will take the property a from the object on the right side of the = and will assign zero only if it doesn't find it. So, it's similar to directly getting the properties:

function explicit(obj) {
  const a = obj.a,
        b = obj.b, 
        c = obj.c, 
        d = obj.d, 
        f = obj.f;
  console.log(`const a = obj.a, b = obj.b, c = obj.c, d = obj.d, f = obj.f;
b = ${b}
c = ${c}
d = ${d}
f = ${f}
`);
}

const obj = { a: 1, b: 2, d: 4};

explicit(obj);

This doesn't have a fallback to zero just for clarity as to what is happening. A fallback value can be done with the conditional operator ? : which will look like this:

const obj = {b: 2};

const a = obj.a ? obj.a : 0;
const b = obj.b ? obj.b : 0;

console.log(`Using the conditional operator "? :"
a = ${a}
b = ${b}
`)

Alternatively, there is an idiomatic usage of the OR operator || that can also produce a fallback value:

const obj = {b: 2};

const a = obj.a || 0;
const b = obj.b || 0;

console.log(`Using the OR operator "||"
a = ${a}
b = ${b}
`)

These aren't completely the same as providing a default value in destructuring but at least close enough for illustration of the alternative. Difference is in how falsy values are handled but we can ignore that for now.

So, with this in mind, destructuring is far less verbose than the normal way:

const a = obj.a || 0,
      b = obj.b || 0, 
      c = obj.c || 0, 
      d = obj.d || 0, 
      f = obj.f || 0;

//compared with

const { a=0, b=0, c=0, d=0, f=0 } = obj;

Secondly, why is it that the reduce method returns one object containing all the grades with their corresponding quantities as opposed to a separate object for each grade?

Well, this is how Array#reduce works. I'm going to simplify things a bit and skip over irrelevant details for brevity - feel free to read through the MDN documentation as it's vastly more thorough than I will be here.

The form of reduce is:

<array>.reduce(callback, initialValue)

Where youu supply it with a callback function that will be called once for each item in the array with two parameters:

function callback(previousResult, currentItem){}
  1. The previous result of callback function. Unless this is the first time it's called in which case it will use initialValue supplied to .reduce.
    • note - previousResult is very frequently named prev for "previous" or acc for "accumulator". I chose the long form for clarity but as you can see the one in your code is acc - it is an idiomatic name for this parameter.
  2. The current item being operated on. Items will be visited one by one in sequence.

Quick illustration using a simple reduce to sum all items in the array:

callback = (acc, currentNumber) => acc + currentNumber;
initialValue = 0;
[1, 2, 3].reduce(callback, initialValue);

then the steps .reduce would are illustrated here:

                    [3,   5,   7]
                     ^    ^    ^
----------------------    |    |
acc =           0 |       |    |
currentNumber = 3 |       |    |
result =        3 |       |    |
-------------------       |    |
                          |    |
---------------------------    |
acc =           3 |            |
currentNumber = 5 |            |
result =        8 |            |
-------------------            |
                               |
--------------------------------
acc =           8 |
currentNumber = 7 |
result =        15|
-------------------

The same applies with the code in the question - each time only a single object is produced by the callback, so next time it's called acc would get a single object again.

Finally, the way the object is updated by using the object spread notation ... to clone and modify a value. {...acc} will create a new object that has the same values as the previous one and {...acc, a: a + 1} will clone it and change the property a to the value of the variable a plus 1. If there was no a property before, then it will be added and since the variable a would be zero, then you'd get { a: 1 }.

const initial = { a: 5};

const obj1 = {...initial, a: 6};
console.log("obj1", obj1);

const obj2 = {...obj1, b: 1};
console.log("obj2", obj2);

const obj3 = {...obj2, b: 2};
console.log("obj3", obj3);
VLAZ
  • 18,437
  • 8
  • 35
  • 54