About the Refactor Global Variables Out of Functions

I don’t know why it always run in here, I review it multiple times.

[https://github.com/freeCodeCamp/freeCodeCamp/issues/13157]
I searched the forum, the address is above, the answer should be two part.

  1. Refactor Global Variables out of Functions
  2. Avoid Mutating External Variables

I think I done it all.
Your code so far


// the global variable
var bookList = ["The Hound of the Baskervilles", "On The Electrodynamics of Moving Bodies", "Philosophiæ Naturalis Principia Mathematica", "Disquisitiones Arithmeticae"];

/* This function should add a book to the list and return the list */
// New parameters should come before the bookName one

// Add your code below this line
function add (bookLists,bookName) {
  var mybook = bookLists.slice(0);
  return mybook.push(bookName);
  
  // Add your code above this line
}

/* This function should remove a book from the list and return the list */
// New parameters should come before the bookName one

// Add your code below this line
function remove (bookLists,bookName) {
   var index = bookLists.indexOf(bookName);
    var mybook = bookLists.slice(0);
    return mybook.splice(index, 1);
    
    // Add your code above this line
}

var newBookList = add(bookList, 'A Brief History of Time');
var newerBookList = remove(bookList, 'On The Electrodynamics of Moving Bodies');
var newestBookList = remove(add(bookList, 'A Brief History of Time'), 'On The Electrodynamics of Moving Bodies');

console.log(bookList);

However the result is like this
// running test
bookLists.indexOf is not a function
bookLists.indexOf is not a function
bookLists.indexOf is not a function
bookLists.indexOf is not a function
// tests completed

and all tests don’t pass.

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36.

Link to the challenge:
https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/functional-programming/refactor-global-variables-out-of-functions/

Look into what exactly is returned by mybook.push(bookName) and mybook.splice(index, 1). It’s not what you would expect.

And since you’re not returning what you think you are, this test function does not pass the right value to remove

remove(add(bookList, 'A Brief History of Time'), '...);

But I’d also counter that you don’t need .push() or .slice(), even if that’s how they started you off.

Functional programming is declarative, whereas most programming is imperative.

  • declarative

    • telling the computer what you want done.
    • the functions you call handle all the steps to get it done.
  • imperative

    • telling the computer how you want it done.
    • this means you have to write all the steps that are required.

Knowing this, are there any methods on the array object that you can use? Let those functions do all the work of combining and filtering data.

Remember you’re starting with an array, and you’re using array methods. So you may need to convert other things into an array as well.

Look through the list on the lefthand side here. It’s not long, and you’ll find two methods to pass the test.

4 Likes

Thank you very much, it turns out that the push returns the length, and the slice only return the delete elements. The original method deliberately guided you to a wrong way.

right now, my code pass!!!

I trying to understand the difference between declarative and imperative programming.
I hope I can find the method you talked about.

Thanks!!!

1 Like

I’ll try to explain it and show the difference. But don’t worry if it doesn’t click right away. It’s not an easy concept to grasp quickly.

And don’t feel the need to learn this right now either. It’s not critical. Feel free to let me know if there’s something I didn’t make clear.

I blurred it cuz it contains spoilers.

Two of the main concepts of functional programming are
1. everything is data. We use functions to modify and display that data. Like a sculptor molds clay, so you must mold the data.
2.\ the original data must not be changed (immutability)

So let’s take a look at the requirements. I always list them out like this so that I can knock each out as it’s own function.

If any step requires more steps, then I break it down further. But none of these do.

1. the global array bookList is not changed inside either function
2. The add function should add the given bookName to the end of an array
3. The remove function should remove the given bookName from an array.
4. Both functions should return an array
5. new parameters should be added before the bookName one

Since 1 is an array, and 4 requires we return an array, we know that we can use array methods. Step 5 is just guidance on the parameters. So really we only have 3 requirements

1. Use array methods
2. The add function should add the given bookName to the end of an array
3. The remove function should remove the given bookName from an array.



Ok. So if we were going to code this imperatively, for step 2 (the add function) we’d be saying to ourselves

Using an array of books and the name of a newBook
1. make a copy of the books array
2. push the newBook to the books array
3. return to me the copy of the books array

Whereas declaratively, we’d be saying

Using an array of books and the name of a newBook
1. give me a book array with the new book merged to the end

And that’s it.

But you may wonder if the declarative version is just the question rephrased. It is.

The difference is the key word I used — merged. Remember how I said in functional programming you can only modify data? That means we can’t add to an array. We must merge new data in with the old data, into a new container (immutability).

So once you realize that all you really have to do is merge two arrays together, you can then use the .concat() method on the array object.

From mdn:

The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.

And we get a bonus. Since it returns a new array, you don’t have to worry about creating a copy.

You would then use it like this:

function add (bookLists,bookName) {
// merge an array with bookName into bookLists
  return bookList.concat([bookName])
}

See how I put the bookName inside an array literal? That’s because .concat() merges two arrays. So I just created one to take advantage of the array method.

You can do this any time you feel that using an array method will make things easier. Creating arrays are inexpensive. Browsers can create over 50 million of them a second.

This is declarative because we’re not telling the computer how to create a copy or how to attach the new item. We just say “hey, concat (merge) this new book to the end of the booklist”


Here is how we would do the remove function declaratively

Using an array of books and the name of an existing book
1. give me an array with that item filtered out.

Why filter? Because filtering is just another name for removing things. Now we can use filter() method because it also returns a new array.

function remove (bookLists,bookName) {
// give me all the books that don't match the bookName
  return bookList.filter(b => b !== bookName)
}


Why this song and dance? Because once you grasp the concept, it will lead to clearer and more maintainable code.

What you should do is look over these methods and create a mental map of what they do, using everyday words that make sense to you. My most used and preferred ones are:

  • array.map() — transform every item in the array according to some logic
[1,2].map(it => it * 2) // [2,4]
[1,2].map(it => ({ it: it * 2})) // [{ 1: 2 }, { 2: 4 }]
  • array.filter() — remove any items I do not want
[1,2].filter(it => it !== 2) // [1]
  • array.concat() — attach these two arrays together
[1,2].concat([3,4]) // [1,2,3,4]
  • array.reduce() — take all of one thing, and give me back one of something
[a, b].reduce((prev, curr, idx) => {
           prev[curr] = idx
           return prev } ,{}) // {a: 1, b: 2}

These are the main array methods I find myself using.

.map() and .reduce() are some of the most powerful methods. And if you can get a good grasp on them, you’ll be able to solve many problems with them.

19 Likes

Thanks so much!
I now understand the declarative and imperative programming. In JavaScript, I should write code use the method the MDN provided. It is imperative programming.

I like your leaning technique, it really great!!

about the reduce, I think I do not understand clearly.

why the reduce you return is array with key, but the MDN example returns only numbers.
is it cuz the {} you used as the second argument in the reduce()?

arr.reduce(callback[, initialValue])
I don’t fully understand the syntax, the callback is a function, you use arrow function. The sceond is initialValue, but you set it as a null array.
why the key start at 1?
isn’t it should start at 0?

Exactly! The power of the reduce function is that you can start with whatever object you desire, in whatever shape you desire.

I purposely returned an object to show just that. Good eye picking it up. This is a good indication that you can form mental models.

Just to be clear on this, the methods you use don’t determine if it’s imperative. Rather, it’s the patterns you use that determine if it’s declarative or imperative.

The code you used initially was functional as well. Just not as functional as using the methods I showed. This was more of a higher level abstraction.

I didn’t start it with a null array, because like you noticed before, I started with an empty object.

An arrow function is an anonymous function. So writing these are equivalent

const myReducer = function() {}
const myReducer = ()  => {}

// reference the function
[1,2].reduce(myReducer)

// I can also just place the function directly in the callback
// then I don't need to assign it to a variable
[1,2].reduce(function() {})

// or a fat arrow for readability
[1,2].reduce(() => {})

Because if you provide an initial value to the reduce() method, that will be passed in as the first value. So mentally, it would look like this

// passing an initial value
[ {}, 1, 2 ]

// no initial value
[ 1, 2 ]

So the local variables prev is referencing the previous value, and curr is referencing the next value being passed in. The numbers you saw were not index numbers, but rather the numbers in the array. Take a look at it again. I changed it to make it clearer.

If you wanted to get the index, then it would look like this

[1,2].reduce((prev, curr, index) => {})

Thank you :blush: I’m just glad my mental models work for you.

1 Like

Thank you so much!

I learn a lot. I now know that the prev at first is {} object, and in JavaScript the object store pattern is like hash map, not like array. Therefore,
A={};
A[4] =1;
They are valid, although the A don’t have index 4, it will automatically add it. A don’t need contain 1 to 3 index at first. It’s not array.

I fully understand the reduce method and the object in JavaScript.

Thanks so much!!!

1 Like

Exactly! Glad to be of help

1 Like

Thank you! This was very informative.

1 Like

Hint: There are time you have to think another way around to find the solution… its not complex.
I use pure logic to provide a solution to this problem and KISS principle. as JM-Medez mentioned functional programming is declarative and that was already a big hint!

The global variable is: var bookList
In your function parameter you have bookList(s). I think if you change this code to bookList instead of bookLists it should work.

This is one of the best explanations I’ve ever seen about concat, map, filter, reduce, etc to make functional programming easier. It felt like you took my hand and pointed every detail!

Thanks @JM-Mendez

Your answer should be the hint page for this exercise, but it also could be a post at https://medium.com/@FreeCodeCamp

1 Like

This was a very good explanation, thank you very much, but I don’t get the line
return bookList.filter(b => b !== bookName)
I’m confused with what’s in the brackets.

This was a very good explanation, thank you very much

You’re welcome :slightly_smiling_face:

but I don’t get the line
return bookList.filter(b => b !== bookName)

This line is using es6 arrow function syntax

It removes many issues with accessing this within functions, and also makes for terser code. You can write this function using es5 syntax like this

return bookList.filter(function(b) {
  return b !== bookName
})

And when seen extracted into a separate function, would look like this

// es6 arrow
const filterFunc = b => b !== bookName

// es5
function filterFunc(b) {
  return b !== bookName
}

Right! Now this is more familiar. I’m still new to the arrow functions so they can sometimes baffle me. Thanks once again!

1 Like