Jonas: Welcome back to continuing to fix the code
that we started to work on a bit earlier.
And so in this lecture, we're gonna focus on
some of the functional and declarative principles
that we learned about in the last video.
And we're gonna focus on three big areas
of functional JavaScript, just as we talked about
in the last lecture.
So that's first immutability,
second, side effects and pure functions,
and third, making data transformations using pure functions
such as map, filter, and reduce.
And let's actually start with immutability
because in JavaScript there's actually a way
to make a data structure, or in other words
to make an array or an object immutable.
So let's take a look at that.
So let's go back here.
And so what we can do is to call the function Object.freeze.
Then in that function we pass in the object
that we want to make immutable, basically.
So by doing this, spending limits is now immutable,
which means that we can no longer
put any new properties into it.
So let me show that to you.
But actually, this works best in strict mode,
which I'm noticing here is actually not activated
in this script.
But we already know how to do that, right?
And so let's see what happens
if we try to add a new property to spending limits.
Let's say we want to add Jay actually,
'cause we have him down here.
And so let's say we want to actually allow him now
to spend something as well.
So let's say 200, let's give it a save.
And if we now check out this object,
then you see that it didn't change, right?
And so that is Object.freeze for you.
So whatever we try to add now to this object
is not going to work because it has been freezed.
So let's do the same with this one here,
because in the end, an array is also just an object, right?
And so we can also do Object.freeze on arrays.
Just like this.
And now, we actually already start running into trouble
because down here we are trying to push something
into this array, which is now no longer possible
because again we made it immutable by using Object.freeze.
So right out of the gate,
this code here is now no longer working.
Now, I actually wanted to show something to you,
so let me remove that,
but there's probably still gonna be an error down here.
Well, actually not.
And yeah, there's a reason for that
which I'm gonna describe now.
So you saw that we can not add new elements
to this object now, right?
So that was the reason for the error that we just saw here.
However, Object.freeze here basically only freezes
the first level of the object.
So it's not a so-called deep freeze
because we can still change objects inside of the object.
So for example, I can do budget[0].value,
I can set it to whatever I want.
And so if we see now the result here,
you see that the value did indeed change, alright?
What I cannot do is to add a completely new element.
So I cannot say, let's say [9] = 'jonas'.
So that is not going to work.
So you see nothing was added here now.
Alright?
So that's just something to keep in mind
when you use Object.freeze.
Now, there are again third-party libraries
which implement a deep freeze,
but I'm not gonna go that way here
in this small and simple example.
Let's just take out this one here and then let's keep going.
All right.
So let's put this back
and that will then bring back or error.
And so now we will need to fix this function.
So how do we fix this function
and why does it create the error in the first place?
So the problem is that right now, this function addExpense
is trying to mutate this outside object.
So in other words, according to what we learned
in the last lecture is that this function right now
has a side effect, right?
So remember that a side effect basically means
that something outside of a function is manipulated
or that the function does something
other than simply returning a value, right?
And so a function that has or that produces side effects
is called an impure function.
So this function addExpense right now is an impure function
because it does attempt to manipulate and to mutate
this object that is located outside of it.
So how do we fix that?
Well, first of all, we should always pass all the data
that the function depends on into the function
so that it doesn't have to reach into the outer scope.
So that's the first good practice.
And then, of course, the function should not change
any of these values.
So in other words, it should not mutate them.
And that's the whole reason
why we made this object here immutable,
just so we cannot by accident mutate this object, right?
So remember that the solution to that is to create a copy
and then return that copy of the state.
So of the data, right?
So I just said a lot of stuff.
So let's put all of that into practice here right now.
So the first thing that I'm gonna do
is to pass in a variable called state
and then also the limits here, okay?
So this state here will be the budget object,
and then here the limits
is, of course, going to be the spending limits.
And so I will pass in all of that here
in all of the three function calls.
So budget and spending limits.
So that, of course, alone does not fix the error,
but we are working in the right direction.
Okay?
Now, I said in the last lecture
or in one of the previous ones
that we should not pass more than three arguments
into a function.
Now, in this case, here we actually have five parameters,
but, well, sometimes it's not a big deal
to break those rules.
We could also pass in simply
one object of options, basically,
but let's keep it simple here.
I'm not into the mood of changing all of these inputs here.
All right?
So this is just fine here.
Then, next up, here we are clearly manipulating
the user variable.
So the user argument in this case.
And so as we learned in the last video,
we should avoid these data mutations whenever possible.
And so let's instead simply create
a new variable right here.
So let's call this one here cleanUser
because we are basically cleaning it.
This is a common terminology that we use.
So this is the whole reason why this Matilda here,
even with the capital M still works.
Okay?
So I did mention that in the previous lecture
where we worked about this,
but without this piece of code, it wouldn't work
because Matilda here is uppercase,
but here in the object it is lowercase.
And so with the uppercase,
it would not be found here in the object
because this here is lowercase.
And so that's, of course, different.
And so here we will always convert everything to lowercase
so that it then corresponds to the object keys
that we have here.
So this one here is from before,
let's actually put it here where it belongs to.
So that was the alternative way of getting the limit.
Then here we need to use that cleanUser at the same here.
So we still want this to be called user.
So here we need to go back to saying user
should be equal to the cleanUser.
Okay?
So we did most of the work here,
but now, actually, comes the main part.
And again, that is that we want to replace
this manipulating of the object by creating a new object
based on the state that we receive.
All right?
So let's get rid of this code here.
And instead, what do we want to do?
Well, we basically want to return an array like this here,
but simply with one more object.
So we learned already how to do that actually previously,
and it's not very difficult.
So let's just say we want to return then an empty array,
and then we use the spread operator to put all the elements
of the state in there.
And so this effectively creates a copy of this state array.
Remember how we did that earlier?
And so this is exactly the same technique right here.
And now, all we need to do is to then add
a new element right after this one.
And that element is gonna be this object
which looks exactly like what we had before.
Alright?
And so, now, we can get rid of this.
Alright.
So right now, calling the addExpense function
will actually no longer mutate the budget object, right?
And so, therefore, if you want to then do something
with the new budget,
we actually need to store that somewhere.
So let's say newBudget1 is equal to this,
then let's quickly log it to the console as well.
And so let's see, and here we are.
So here is pizza.
So that's just the expense that we added here previously.
Great.
But now, what happens if this here turns out to be false?
So basically if this code here does not run.
Well, let's try that
by making this pizza here ridiculously expensive.
And so you see that now we actually get undefined,
so that's not good, right?
And so what do you think we should return in this case?
Well, what we have to do is to return the original state,
so the original budget that we got in, right?
And so in that case, the addExpense function
will always return something.
So it either returns the original budget
or it returns the one with the new expense added to it.
Right?
So let's clean this up here a little bit
and simply use the ternary operator here
because remember that is a lot more declarative
dandy more old school if-else statement.
So here, let's take away the return
and actually put it here in front.
So here we will now decide what is gonna be returned.
So in this case, what we will return is this new state,
so this new budget,
or otherwise we will just return the original one.
Now, we got some error somewhere.
Ah, it's this one.
And here we go.
So now, we no longer got our error
and we are returning the original budget here.
And if we put it back to a normal price,
then here we go.
So here is our object, our updated expense.
So our updated budget, but in a functional way.
So right now, this function here
does no longer produce side effects.
It is now officially a pure function.
Let's actually write that here
because we're really happy about this now.
Alright, now, what about this one here?
So the next addExpense call.
So do you think we still should pass budget
into this one here?
Well, not really, right?
Because this one will then again act on the original budget.
And that doesn't really make a lot of sense, right?
Because this previous expense that we just added
is not gonna be in there, is it?
So let me show that to you, actually.
Put that down here and create a new one.
So let's see.
And so here now we have going to the movies,
but indeed, we don't have the pizza here before.
And that makes sense, right?
Because we are again using the original budget,
which was never mutated by the previous addExpense call.
Right?
And so what we have to do instead
is to hear pass in the result of the previous operation.
So that's newBudget1.
So if we see now,
now it will have indeed pizza and going to movies.
And then, of course, the same thing here
for the final part of the puzzle.
So the final expense, the third one.
And here then we again use the previous one.
So that's newBudget2.
But now, this one here should be the same as before
because, of course, Jay is not allowed to add anything.
Right?
So only pizza and going to movies has been added there.
Let's make this one here a bit smaller
so that it fits in one line.
Well, it didn't.
All right, so let's just leave it like that.
And yeah, so that first part here is now fixed.
Now, in the real-world
we would use something called composing
and a technique called currying
to basically create this chain of operations here.
So here we need all of these intermediate variables
to create a new budget, right?
So we call this once store the result in a new variable.
Then the next call we use that previous variable.
And then, again, in the next call
we use that previous variable again.
So this is kind of a chain of addExpenses
and it would be nice to always automatically
use the previous result for the next operation.
And so again, in a real-world, big functional application,
we would use composing to create one function
which will then perform all of these operations at once.
But that would really be outside the scope of this course
because that is a whole big world on itself.
So here we are just trying to implement
some of these techniques and some of these concepts
just so you see how they work, and that they exist,
and that they are important.
And again, this becomes especially important
once you start using one of the frameworks out there,
just like react or redux,
which rely heavily on concepts such as immutability
and pure functions.
But anyway, let's now move on here in our code.
And now that we already talked so much about side effects,
and immutability, and pure functions,
let's turn our attention to data transformations
here in this case
because as you see here we have this for-of loop
which we can easily transform
into a functional function basically.
So we have this loop here which loops over all the entries
in the budget, right?
So basically, which loops over this array,
and in each iteration the current one
is one of these entry objects.
And then, in this loop, it will update each of the objects
to contain the flag attribute, basically,
whenever the value is over the limit.
So that happened for example here.
And so we got flag set to limit,
remember that from the previous lecture.
But, of course, this function is again an impure function
because it does manipulate the object itself, right?
So in this case, it works with a budget
but the same would be true if we wrote newBudget3 here,
which was our latest and updated budget.
So this one here actually comes from line 65,
so it's this one.
So what I would be interested here is this.
So let's move that down here.
And so here indeed we will now see,
yeah, so here is flag set to limit.
And so we violated the principle of immutability here.
Right?
And therefore, let's transform this function here
into a pure function as well.
So again, we will want to pass in all the data
that the function depends on.
And so that's, again, well, in this case,
it is Budget3.
So newBudget3.
And also, we actually need the limit from the outside.
And actually, we skipped this function here,
but let me come back to that in a minute.
So let's just pass in the spending limits here as well.
So let's go back up here.
So we passed in the spending limits here
into the addExpense function, but we actually never used it.
And so the reason for that is,
is that getLimit here actually uses the spending limits
from the outer scope.
And that's something that we do not want, right?
And therefore, let's add the limits here
as another parameter, limits and user.
And then here I can just call it limits like this.
And then here I pass the limits in, alright?
So it works just like before,
but now, this function here no longer depends
on any external variable.
It can do all its work without having to look up
in the scope chain.
Now, okay, so let's do the same here as well.
So here, we want to then receive a state,
I'm gonna call it state to keep it neutral,
just like before.
And then calling it limits once again.
So here again, we want to pass in limits
and then the user itself.
But now, let's then finally actually replace
this entire loop and instead loop over the state
using one of our data transformation functions.
So what do we want to do here?
Well, we want to basically keep an array
of the same size here in the end, right?
So in each of these objects,
we basically just want to add a property,
but that's all we want to do.
We don't want to filter anything
or we don't want to use reduce either, right?
So it makes sense to use the map method here
and map, remember, will create a brand new object.
So brand new array.
And so that's exactly what we want, right?
So that's in the spirit of functional code
and of immutability.
So not mutating the state, but instead creating a new state
based on the original one.
And so in fact, this is then the state
that we are going to return from this function,
but let's not get ahead of ourselves
and instead work here on this.
So let's create a function block here
and think about what we want to do here.
So right now here inside of this callback,
each entry is one of these objects, just like this, right?
But of course, we still do not want to mutate
any of these objects, right?
Instead when this condition here is true,
so when the value is less than the limit,
then essentially we want to copy the object
and add the new property onto that copy, right?
So basically just like we did here,
so the idea is the same.
So here we copied the entire array
and then added a new object.
But here what we will do
is to basically copy the entire entry object
and then add the flag property to that.
So that sounds a bit complicated, but trust me,
it will all make sense.
So let's copy this condition here, just like this,
and then again making the code nicely declarative
by using the ternary operator.
So if this condition is true,
so if the value is above the limit,
then here we want to return the original object copied.
So remember that this creates a copy and add a new property.
And so that's flag: 'limit'.
And otherwise, what we will want to do
is simply return the original entry.
And that's actually it.
So let's just check out the result
and then I will show it to you again.
All right, so here now, of course,
we need to again store the results into a variable.
Let's call this one finalBudget
because we will not create any new budget.
And I'm then gonna log that here.
And so let's see if we got the flag here.
And indeed flag is set to limit.
So that worked.
And you see that we did not actually manipulate any object.
All we did here was to create a copy
and then add the property onto that copy.
Right?
Now, here you might not be used anymore
to actually see the return here
in the map callback function,
but that's just because we have this function block here.
Right?
And so remember that in the map function,
whatever is returned from the callback will be the element
in the same position of the new array, right?
So that's why we need the return here.
But, of course, we could also completely simplify
all of this here if we wanted to make it simpler.
So let's take this
and actually, transform it into an arrow function
if you prefer that.
So I will then leave these two here.
So function and state, then let's say just like this,
then we don't need the return.
And here, we can get rid of the braces as well
and of the return.
Let's call this one here, two.
And here we don't need that semicolon, I guess.
And that's it.
Now, we're ready to go.
And this is basically the same function,
just copy it or actually comment it out.
So just in case you prefer this arrow function.
So I hope that this made sense.
What's really important to note here once more
is that we transformed this function here
into a pure function, which does not mutate anything
because the map method here returns a brand new array.
So we give this function an array
and it will then create a new one
simply by mapping over the original one,
which again creates then a brand new one.
And in each position of the array,
we then either return a copy of the original entry
plus the flag property,
or simply we return the original entry as it was.
And so with this, again, our function is nice and pure,
and does not create any side effect,
and does not manipulate anything.
Nice.
And maybe you're actually noticing
that throughout most of the course.
We actually kind of followed these principles already,
but without calling it functional.
And I remember especially the Bankist project,
so in a section about a race.
So in there, I told you a lot of times
that we should always pass all the data that we need
for a certain function to work right into that function.
So that, again, it does not depend on any outside data
because really you will see in practice
that this makes it a lot easier
to reason about the function itself.
And so if you do that for all of your functions,
then in the end, that will make your code
a lot easier to understand and a lot more readable now.
Okay, but with that being said, let's now move on here
to our final function.
And here, again, we have this for loop.
And inside of that loop,
we are actually constantly manipulating or mutating
this output variable here.
And that, of course, goes against
the spirit of immutability.
Right?
So immutability is, of course,
not just for objects and arrays,
this also goes for more regular variables.
And so in functional code,
you will probably never see the let variable.
So we're always trying to compute a variable,
for example, based on methods like this one here, right?
And so here we will actually be able to achieve
something similar.
So let's think together
how we could create a string like this here
simply based on pure and functional functions.
So data transformation functions for arrays
and also for strings, actually.
Let's comment all of this out
because, of course, it's gonna be a better,
more declarative and functional way.
So in the code that we had here before,
of course, it was very imperative,
we said create an empty variable,
then we manually said that it should loop over
all of the entries in the budget array.
And then whenever we had a big expense,
which is basically this one here,
it should then add the emoji to the output variable here.
So to this string, right?
But if we really think about this,
there is a better way to doing this.
So this one here is called output,
and I guess we should just actually call it bigExpenses.
So you could have done that before.
So giving it a better name.
So what we really want to do,
and this now, I think, requires a little bit more thinking
on our part, but believe me, it's gonna be worth it.
So what we really want to do here
is not all of this imperative stuff.
So what we want to do is actually just filter our array
for these bigExpenses.
And then, basically, for each of the results,
all we want to do is to create a nice string
containing only the emoji.
So let's translate what we just said
and starting with the filter.
So entry and then here the condition
is, of course, gonna be the same.
So basically an expensive or a bigExpense.
So let's start by logging this here
just so we can see what we're doing, bigExpenses.
And here we say that the state is not defined,
and that's simply because I didn't pass it in yet.
So just like before, of course,
we want to make this function not to depend on any variable
from the outside.
That's how we pass it in here as finalBudget.
So let's see what we have here.
And indeed that is the New iPhone and Laptop,
which are exactly our bigExpenses.
And so, now, all we need to do
is to get these emojis here out
and then create a nice string based on the result, right?
So we have now this array of two,
and so we want to again create an array of two,
but only with these emojis.
So let's use map for that.
So again, that is an entry
and here we want the entry.description.
And then just take the last two characters.
So just like we did here before.
All right?
And now, we can just join that like this,
as we had before.
And there we go.
So this here is essentially the functional version
of just doing this here.
So after this video, you can maybe take a better look
and really compare these two things that we did here.
Lemme just show you that we could also have done it
in another way.
So instead of map and join,
we could have done it all in one go using reduce.
And so once again, this is a nice use case
of showing you a more advanced way of using reduce.
So again, the use case of reduce
is to basically take all the values in one array
and create one value out of them.
So reduce them all into just one.
And in this case, what we want is our string.
And so that's gonna be the accumulator.
And then as always, we have the current value,
then here a call back, and then the initial value,
which is just gonna be an empty string.
So what we want to do here is to basically always return
the previous string plus the current string.
So that's the previous string,
and then the current string like this.
And of course, we still need to say .description.slice,
like this.
So we're almost there already, just missing this separator.
And there we go.
Now, we just have this ugly thing here.
Let's see what happens if we remove this here altogether.
And yeah, of course, that cannot work.
We always need an initial value.
And so now we could go ahead and only start reading here,
but that's not really necessary.
So we can, of course, go back to using just this code,
but that's not gonna be necessary.
Of course, we can just go back and use this code right here,
and take out this one.
So this was just yet another use case of the reduce method
that I wanted to show you here
so that we can create even strings using reduce.
Alright, so I'm leaving this here again
for you to compare, give it a save.
And I think that with this we are done.
So we transformed our initial code, which looked pretty bad,
first by applying some general guidelines
for modern and clean code.
And then, now, in this video, we made our code functional
and took out all of the impure functions,
and side effects, and data mutations.
So a lot cleaner, a lot nicer.
And also if you ask me a lot more professional-looking code.
Now, actually, this function here is not pure.
Let me just write that here.
So it's an impure function
because it creates a side effect
by doing this console.log here.
Okay?
So in fact, all console.logs here are, of course, impure
because they do something.
So they create something here in the console in this case.
So they create input/output.
But, of course, any program needs to have some side effects.
Because otherwise, what is the point
of the program in the first place?
If we didn't have all of these console.logs,
how would we even know that the program is running
in the first place?
Right?
It would not be useful at all.
And so as I said, we always need some side effects,
but in functional programming,
we try to push these side effects as far to the edge.
So as far to the end of our program as possible.
So without having them all over the place,
polluting our application.
Now, this kind of mini-project here
is, of course, very far from being complete.
And if you wish,
you can, of course, develop this a lot further.
For example, you can add a function for adding the income,
or you can also create functions for calculating
the total expenses and incomes, the overall budget,
how much the expenses are in percentage of the income,
and so on and so forth.
But I will leave it like this here
because again, this is enough for us now
and our code looks pretty professional as it is.
Just keep in mind again that these are more like guidelines
and not really hard rules.
So large applications,
they're very hard to make 100% functional
and that's absolutely okay.
So actually, right in the next project,
we will already break many of these rules.
And, again, that's okay.
It's not a big problem.
As long as we try to still keep some of these principles
in some of the places,
we're already making our code a lot better
and a lot more professional.
Now, okay.
And with that being said, you are now ready
to go build the biggest and kind of the final project
of this course in the next section.
And so I really hope to see you there soon.