Table of Contents
-
Functional Programming
- FP Basics
- What is ES6 Javascript?
- What programming paradigm are exisiting
- Foundations of FP
- Why FP?
- Lessons Recap
- Functional JS Syntax
- Array Methods
- The Filter Method
- The Reduce Method
- Array Methods for Selection
- ES6 Variables
- Higher-Order Function Intro
- Functional Data Manipulations
- DOM manipulations
- Going Further with Functional JS Intro
- Course Recap
Functional Programming
FP Basics
- First and probably most importantly, having a solid understanding of another programming paradigm will help you write better code - even if you never end up using a functional language or framework in your job.
- It is on the rise, more languages are choosing at least functional-inspired styles, and understanding where these ideas are coming from will help you learn them faster
- It will make learning React less mysterious. React and Redux both heavily ascribe to functional principles.
FP History
What is ES6 Javascript?
Certainly! ECMAScript 2015, often referred to as ES6 (ECMAScript 6) or ES2015, is a major update to the JavaScript language specification. It introduced several new features and enhancements to make JavaScript code more expressive, readable, and maintainable. Here are some key features of ES6:
- Let and Const Declarations:
let allows you to declare variables with block scope, replacing the function scope of var. const is used to declare constants, which cannot be reassigned.
let variableName = 'Some value';
const pi = 3.14159;
- Arrow Functions:
Arrow functions provide a more concise syntax for defining functions.
// Traditional function
function add(a, b) {
return a + b;
}
// Arrow function
const add = (a, b) => a + b;
- Template Literals:
Template literals allow for more flexible string formatting, including multi-line strings and embedded expressions.
const name = 'John';
const greeting = `Hello, ${name}!`;
- Destructuring Assignment:
Destructuring enables you to extract values from arrays or objects and assign them to variables in a more concise way.
// Array destructuring
const [first, second] = [1, 2];
// Object destructuring
const { firstName, lastName } = { firstName: 'John', lastName: 'Doe' };
- Default Parameters:
Default parameter values allow you to specify default values for function parameters.
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
greet(); // Outputs: Hello, Guest!
- Spread and Rest Operators:
The spread operator (...) can be used to spread elements of an array or object into another array or object. The rest parameter (...) allows you to collect function arguments into an array.
// Spread operator
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
// Rest parameter
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
sum(1, 2, 3); // Outputs: 6
- Classes:
ES6 introduced a more class-oriented syntax for creating objects and constructor functions.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
const john = new Person('John', 30);
john.greet();
- Promises:
Promises provide a cleaner and more structured way to handle asynchronous operations.
const fetchData = () => {
return new Promise((resolve, reject) => {
// Asynchronous operation
if (success) {
resolve(data);
} else {
reject(error);
}
});
};
fetchData()
.then(result => console.log(result))
.catch(error => console.error(error));
Recap
In this lesson you have learned about the following!
Course introduction
- Functional Programming Basics
- History
- Course Overview
- Baseline Questions Next up, we’ll cover programming paradigms and the foundational concepts of Functional programming.
Glossary
- Functional programming - a programming paradigm that keeps the processing within functions. Note: This will be expanded upon in the following lesson.
- Lambda Calculus - a mathematical idea that any computation - any program - can be expressed as a function or set of functions.
What programming paradigm are exisiting
That was a lot of information, so here are the main points again:
- Programming Paradigm is a philosophy, style, or general approach to writing code.
- Most programming paradigms differ in how they deal with state.
- The Imperative Paradigm solves problems with an explicit sequence of commands to get from point A to point B. If the code or comments read like a recipe or handbook, it's likely to be imperative. Many programming languages, especially dynamic ones, including Javascript, PHP, and Python can be used to write imperative programs.
- The Functional Paradigm solves problems with functions that hold simple pieces of logic and can be chained together to accomplish more complex actions.
- The Object-Oriented Paradigm solves problems by defining objects that can hold both values (properties) and functionality (methods).
List of different programming paradigms
Paradigm Summary
Hopefully, you are beginning to get a clearer picture of what a paradigm is and the range of them that exist, and perhaps I have also convinced you that learning them will benefit your career. But you can relax because we are going to put away the theory for a little while now and in the next section dive into some code examples to compare paradigms.
If you want some extra thoughts to chew on, the article's in the Further Research section below is full of good ideas about paradigms and coding in general.
Comparing Paradigms
Imperative
let name = 'Captain Kirk'
let hour = new Date().getHours()
let greeting = ''
if (hour <= 6) {
greeting = 'Good Morning, '
} else if (hour >= 17) {
greeting = 'Good Evening, '
} else {
greeting = 'Hello, '
}
greeting += name + '.'
console.log(greeting)
//expected output: Hello, Captain Kirk
Functional
const greet = (name) => (salutation) => `${salutation}, ${name}.`
const determineSalutation = (callback) => {
const hour = new Date().getHours()
if (hour <= 6) {
return callback('Good Morning')
} else if (hour >= 17) {
return callback('Good Evening')
} else {
return callback('Hello')
}
}
let result = determineSalutation(greet('Captain Kirk'))
console.log(result)
//expected output: Hello, Captain Kirk
Object-Oriented
class Person {
constructor(name){
this.name = name
}
determineSalutation(date){
const hour = (date && date.getHours()) || new Date().getHours()
if (hour <= 6) {
return "Good Morning"
} else if (hour >= 17) {
return "Good Evening"
} else {
return "Hello"
}
}
greet() {
return `${this.determineSalutation()}\u00A0${this.name}`
// using JavaScript template literal: `${}`
// \u00A0 is a symbol to add a space in the template literal
}
}
const kirk = new Person("Kirk", "Captain")
console.log(kirk.greet())
//expected output: Hello, Captain Kirk
Further Research
I would highly recommend the article, An Introduction to Programming Paradigms, from Digital Fellows by Patrick Smyth, especially the sections following sections:
A Simpler Program, is a great perspective on how to think about your programs. Which Paradigm to Choose?, which gives more information on choosing a programming paradigm. Introduction to programming paradigms
Foundations of FP
Functional Programming Specifics
So we’ve set up where functional programming came from, and that it is growing in popularity, but we haven’t yet discussed any specifics of what it contains. This lesson will focus on the concepts that are core to Functional Programming, like pure functions, side effects, and the idea of immutability. Those might sound like just a jumble of technical words, but I think you’ll be surprised at how familiar they are to you because of the amount of JavaScript you already know!
In the end, everything we learn in this section will point back to the main tenet of functional programming, which is to have confidence in the programs you write because you rely on functions that behave the same way every time. Read on to learn how pure functions and immutability will make your programs more predictable.
Pure functions
Pure functions are a simple concept with big implications. And to start off with, I’m pretty sure you have already written a pure function! If you started off learning to write functions like this:
function add(x,y) {
return x + y
}
Then you have written a pure function! So what makes this pure, and why is this simple idea the foundation for a powerful paradigm like functional programming?
One thing that is obvious in this case, but incredibly important, is that if given the same arguments, this function will return the same value. You can run add(2, 2) as many times as you want, and the only thing you will get is 4. Brian Lonsdorf's definition of a pure function in the GitBook, "Professor Frisby's Mostly Adequate Guide to Functional Programming" is this:
A pure function is a function that, given the same input, will always return the same output and does not have any observable side effect.
Code Review - Side Effects
let galaxy_ship = {
torpedo_balance: 0,
id: 123456,
}
function stock_arsenal(amount, id) {
if (galaxy_ship.id === id) {
// SIDE EFFECT
// Galaxy_ship’s values are being updated, but it is not a part of the return of this function
galaxy_ship.torpedo_balance += amount
console.log('arsenal successfully stocked')
console.log(galaxy_ship.torpedo_balance)
// return value is just a message
return 'arsenal successfully stocked'
}
console.log(`invalid account id`)
return `invalid account id`
}
stock_arsenal(40, 123456)
// expected output:
// arsenal successfully stocked
// 40
Summary on Pure Functions
We just learned about side effects, which modify state and don't come from the return statement of that function. So now we have everything we need to understand pure functions. Same inputs get the same output and the return value is the total effect of running the function.
What do we get from writing pure functions? To sum it up in one word, I would say confidence. But there is a lot more to it than that. Here are some last points to consider:
- If we can count on a function to produce the same result no matter where in the program it runs, then we don’t have to be afraid of calling it anywhere. It makes my functions easy to reuse.
- If a function has no side effects, then we remove the mental load of needing to remember them. I have confidence about what my functions do, and the effect they will have on my app. That confidence means that I can scale things more easily and alter programs with less fear.
Immutability
Another major tenant of functional programming is that we do not “edit” things; we make new things.
For instance, if I had a set of notes for a class, and you wanted to borrow them, when you see something that you wanted to edit or add, you wouldn’t make those edits on my notes...hopefully. Instead, you would make a fresh copy of my notes and make your changes there. That would preserve my copy and let you have your new notes; in essence, that is what functional programming wants.
Editing - more commonly referred to in development as “mutating” the same thing over and over again makes it hard to know what copy you are looking at, and is prone to errors. At multiple points later in the course, we will take a look into how we can create immutable values in our JavaScript programs. For now though, this will hopefully explain why we favor ‘const’ variables over ‘let’ variables in this course.
New Terms
- Pure Functions - A function that will always return the same output if given the same input, and which has no side effects.
- Side effects - An effect on your overall program from running a function, that did not come from the return statement of that function.
- Immutable - Unchanging. Immutable values are ones which, once declared, cannot be changed.
Why FP?
Introduction
In this section, we’ll build from the core functional concepts we talked about in the previous lesson to determine why developers are turning to functional programming to solve some of the common problems in web development.
Functional Pros and Cons
It can be difficult to evaluate the pros and cons of a programming paradigm, and it can be hard to keep each paradigm straight. But one key that can really help is to remember that what typically differentiates paradigms is how they deal with application state - or put more simply - how they keep track of stuff (values and entities) and how those things change while the program runs. How does Functional Programming deal with application state? Very carefully. Functional Programming believes that values shouldn’t be sloppily edited, but rather replaced with fresh values every time. It also ensures that state changes in very predictable places -- the return values of functions. You’ll see that a lot of these pros and cons have to do with how state is handled.
Pros
-
Easier to test Because every function is pure, we know that the return value is the sum of the function, and is the only part that needs to be tested. Functions that avoid side effects are easier to test.
-
More predictable code Functions that avoid side effects and the use of immutable values makes changes in the programs more visible. By definition, pure functions always return the same value when given the same inputs. This predictability is the backbone of success with Functional Programming
-
Easier to edit and expand Because pure functions return the same value every time, and perfectly encapsulate their logic in the return statement, it means that a function could be copied and pasted to a new part of a program, or moved to an entirely different program, and it would still always produce the same result. Functions with side effects cannot be moved without the possibility of breaking something.
Cons
-
More difficult to write in some languages While most modern dynamic languages have the ability to write in a Functional style, it can go against the grain with some. Even JavaScript, for instance, does not have a way to make values truly immutable. Writing programs without classes goes against the grain of Ruby, and PHP does not have all the array methods JavaScript has that follow a Functional methodology.
-
Will never be able to implement completely There is a big difference between pure academic Functional programming and practical Functional-inspired programming for the web. Unless you are using a language that was built to be Functional, like Haskell, there will be times you have to work within the limits of the language to do the best you can at writing functional programs.
-
Few libraries to speed progress (though this is changing) Functional programming for the web is still fairly new, and there are not as many helpful libraries or tutorials for getting started or writing efficiency as there are for say Object Oriented programs, for which we have prebuilt ORMs, tons of tutorials, frameworks, etc..
Lessons Recap
Resources
Here are some of the major resources we used in this lesson:
Digital Fellows Article - Introduction to Programming Paradigms Programming Paradigms Overview
Glossary
- Programming Paradigm - An overarching approach or style to problem-solving or ...
A philosophy, style, or general approach to writing code.
- Pure Functions - A function that will always return the same output if given the same input, and which has no side effects.
- Side effects - An effect on your overall program from running a function, that did not come from the return statement of that function. Immutable - Unchanging. Immutable values are ones which, once declared, cannot be changed.
Functional JS Syntax
Array Methods
Let's start with what was most likely one of the first things you learned to do in JavaScript -- for loops. for loops are great, we seem all the time, but - they aren’t functional. Take a moment and think - what about a for loop wouldn’t be functional?
Functional programming only allows us to use pure functions to do work, so functional programs use recursion rather than loops. A truly functional language won’t even have the functionality of for loops built into the language! In the same way, do...while loops, for..in, and all the other similar looping constructs don’t have a place in Functional programming. So, how do we automatically repeat actions in functional JS? Thankfully, ES6 came packed with functions for doing just that. Get ready for some serious array practice!
The Map Method
Array Map - Basic
const captains = ['Picard', 'Adama', 'Reynolds', 'Beeblebrox']
// create new array
const titles = captains.map(cap => `Captain ${cap}`)
// equivalent to
const titles = captains.map(cap => {
return `Captain ${cap}`
})
console.log('Resulting Array: ', titles)
// expected output: Resulting Array: ['Captain Picard', 'Captain Adama', 'Captain Reynolds', 'Captain Beeblebrox']
console.log('Original Array:', captains)
// is unchanged, expected output: Original Array: ['Picard', 'Adama', 'Reynolds', 'Beeblebrox']
Array Maps - Doubling
const nums = [1, 2, 3, 4, 5]
// Internal callback
// ----------------------------------------------------------------------
const doubles = nums.map(x => x * 2)
console.log(doubles)
// expected output: Array [2, 4, 6, 8, 10]
// External callback
// ----------------------------------------------------------------------
const doubler = function(x) {
return x * 2
}
const doubles2 = nums.map(x => {
return doubler
})
console.log(doubles2)
// expected output: Array [2, 4, 6, 8, 10]
Exercise examples
// 1. Write a map function to reverse this array:
const start = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// your code
const end = start.map(x => -1 * (x - 11))
// expected output: Array [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
console.log(end)
// ----------------------------------------------------------
// 2. Write a map function to print the Job: Name:
const shipMates = [["Mal", "Captain"], ["Wash", "Pilot"], ["Zoey", "1st Mate"], ["Jayne", "Public Relations"]]
// your code
const jobNames = shipMates.map(x => `${x[1]}: ${x[0]}`)
// expected output: Array ["Captain: Mal", etc...]
console.log(jobNames);
// ----------------------------------------------------------
// 3. Write a map function that prints the name: even|odd
const awayTeam = ["Picard", "Riker", "Troy", "Data"]
// your code
const evenOdd = awayTeam.map((x, index) => {
if (index % 2) {
return `${x}: odd`
}
else {
return `${x}: even`
}
})
// expected output: Array: ["Picard: even", "Riker: odd", etc...]
console.log(evenOdd)
// ----------------------------------------------------------
// 4. Create a multidimensional array of each item and its index in the original Array.
const sci_fi_shows = ['Manedlorian', 'Enterprise', 'Firefly', 'Battlestar Galactica']
// your code
const newArray = sci_fi_shows.map( (x, index) => [x, index] )
// expected output: Array [['Manedlorian', 0], ['Enterprise', 1], ['Firefly', 2], ['Battlestar Galactica', 3]]
console.log(newArray)
// ----------------------------------------------------------
// 5. For each item in this array, create a multidimensional array containing the entire original array.
const numbers = [1, 2, 3, 4]
// your code
const newNumber = numbers.map((x, index, array) => [array])
// expected output: Array [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]
console.log(newNumber)
// Having trouble with creating a 2D array?
// Take a closer look at the last two arguments of map, one of them might be able to help
Advanced Map
One of the things that can be hard to wrap your mind around with the map method is what it is doing behind the scenes. So we’re going to walk through it step by step.
You give the map method a function to run on every item in the array - but you never call that function. Instead, the map method calls your function and the important thing to remember is what arguments it is going to pass to your function. It is easy to assume that the map method only passes one argument - the value of the current item in the array - but in fact it passes three. These are the three arguments, listed in order:
- Value of the current item in the array.
- Index of the current item in the array.
- Copy of the entire original array.
The Filter Method
The Filter Method
You will see immediate similarities between the map method and our next ES6 array method: filter. The syntax and mechanics of the two methods are almost exactly the same, but the filter method acts much like a filter in real life. Passing anything through a filter, we leave behind the things we don’t want and get a finished result of only what we do want.
The filter method is a separator, we give it a function with the logic to distinguish the items we want, and filter runs items in the array through that function. In the end, it creates a new array that contains only the elements that our function allowed. So the ONLY difference between filter and map is that map performs a function on every item in an array, and filter uses a true or false conditional on every item in the array to decide if that value should be kept or discarded. This means that one key differentiator of the filter method is its callback method must return either true or false. If it returns true, the value will be added to the result array; if it returns false, that value is discarded.
Code Example Filter is easiest to learn when you play around with it. Below, you can see how a filter is applied to the values list. This function is looking to see if each value in the array resolves to ‘true’ or not. So the ‘filter’ function we are running looks like this: (v == true), where ‘v’ is each value in turn. Only array items where this condition is ‘true’ will be included in the new array ‘result’.
const values = ['true', true, 'yes', 'no', 1, 0, 'false', false];
const result = values.filter(v => v == true);
console.log(result);
// expected output: Array [true, 1]
Exercises:
// 1. Find all the words with more than 7 characters
const words1 = ['tardis', 'grok', 'frak', 'blaster', 'klingon', 'shepherd']
// expected output: Array ['shepherd']
const shepherd = words1.filter(v => v == "shepherd")
console.log(shepherd)
// ----------------------------------------------------------
// 2. Find all even values
const words2 = [12, 13, 14, 15, 16, 17]
// expected output: Array [12, 14, 16]
const evenValues = words2.filter (v => {
if (!(v % 2))
{
return v
}
})
console.log(evenValues)
// ----------------------------------------------------------
// REAL LIFE EXAMPLES
// We often use filter to quickly pull all the items that share a status or other characteristic. For instance, create a list of all the active bounty hunters from the array below:
const hunters = [
{
name: 'Greedo',
universe: 'Star Wars',
status: 'active',
},
{
name: 'Boba Fett',
universe: 'Star Wars',
status: 'inactive',
},
{
name: 'Asajj Ventress',
universe: 'Star Wars',
status: 'unknown',
},
{
name: 'Zam Wesell',
universe: 'Star Wars',
status: 'inactive',
},
{
name: 'Jango Fett',
universe: 'Star Wars',
status: 'active',
},
]
const activeHunters = hunters.filter (v => {
if (v.status === "active")
{
return v;
}
})
console.log(activeHunters)
// expected output: Array [
// {
// name: 'Greedo',
// universe: 'Star Wars',
// status: 'active',
// },
// {
// name: 'Jango Fett',
// universe: 'Star Wars',
// status: 'active',
// },
// ]
The Reduce Method
The Reduce Method
The map and filter methods have a lot in common, they both:
- Run a function (callback given by the developer) on every item in an array.
- Pass three arguments to the function (item value, item index, whole array).
- Return a new array.
The reduce method has some slight differences. From the name you might be able to guess that this method boils our array down to a single value.
For example, if you have an array of products, each with a price, you could use reduce to sum the price values of each item and return a single total cost. Reduce has the special ability to keep track of a value that is updated as it iterates over each item in the array. To get the total cost from an array of items with a price value, you would write a function that adds an item's price to the running total and reduce will run that function on each item in the array until it runs out of items and the total is found.
To do this, reduce only passes the callback two arguments, the total so far - or accumulator, and the current item. Another way that it differs from the first two array methods is that it does not necessarily return an array. In the case of the example above, reduce would return a single integer of the sum of all prices.
Code Example Below, you can see how reduce is applied to the sales list. Reduce goes through the list sales and adds the individual value to a running total which is what will ultimately be returned by reduce. The final output of reduce on sales will be 147.49.
const sales = [120.00, 19.99, 3.50, 4.00];
const total = sales.reduce((runningTotal, currentValue) => {
console.log(runningTotal, currentValue)
return runningTotal + currentValue
})
// cycle 1: 120 19.99
// cycle 2: 139.99 3.5
// cycle 3: 143.49 4
// expected output: 147.49
// If you can follow the code above - great job! No one masters the reduce method overnight, but being able to follow the code is the first step. Below are two examples of alternate syntax that you might also see out in the wild. Can you follow these?
// SAME AS:
const reducer = (runningTotal, currentValue) => runningTotal + currentValue;
console.log(sales.reduce(reducer));
// Look! You can even run map, filter, and reduce on array literals!
// SAME AS:
[120.00, 19.99, 3.50, 4.00].reduce((runningTotal, currentValue) => {
return runningTotal + currentValue
})
Examples
// 1. Take this disjointed sentence and turn it into a single string
const text = ['The ships', 'hung in the sky,', 'much the way', 'that bricks don`t']
// Your Code Here
const reduceText = text.reduce( (total, current) => {
return total + current;
} )
console.log(reduceText)
// expected output: "The ships hung in the sky, much the way that bricks don't"
// ----------------------------------------------------------
// 2. Return the winning team
const scores = [
{
team: 'A',
score: 20
},
{
team: 'B',
score: 17
},
{
team: 'C',
score: 23
},
{
team: 'D',
score: 13
}
]
// Your Code Here
const winningTeam = scores.reduce( (previous, current) => {
if (current.score > previous.score)
{
return current;
}
return previous;
})
console.log(winningTeam)
// expected output: "C"
// ----------------------------------------------------------
// REAL LIFE EXAMPLE
// Reduce can sometimes save us a lot of time -- if we remember to use it.
// Instead of writing a complicated map or filter method and then calling the
// name of the ship out of the retuned array, Return the name of the fastest
// star ship
const ships = [
{
name: 'Serenity',
speed: '4.2G',
},
{
name: 'Cylon Raider',
speed: '7.5G',
},
{
name: 'Swordfish II',
speed: '50G',
},
{
name: 'Tie Fighters',
speed: '4100G',
}
]
// Your Code Here
const fastest = ships.reduce( (previous, current) => {
const currentSpeed = parseInt(current.speed.slice(0,-1));
const previousSpeed = parseInt(previous.speed.slice(0,-1));
if (currentSpeed > previousSpeed){
return current;
}
return previous;
} )
// Expected output: Tie Fighters
console.log(fastest)
Summary
Map - The map method iterates over every item in the array it is called on, and performs one action on each item. The action logic is held in a callback function. The map method returns a new array that is the same length as the original array, but with the items updated according to the callback function logic.
Filter - The filter method iterates over every item in the array it is called on, and runs each item through a pass or fail checking logic. The logic is held in a callback function which must return either true or false. The filter method returns a new array that is shorter than the original array (or potentially the same length, if all values passed successfully through the callback), but the values that pass through will be unchanged.
Reduce - The reduce method iterates over every item in the array it is called on, and keeps one value (for instance, the sum of all prices). Each item in the array is forgotten, and the end result is a single value. The uses of reduce vary greatly depending on your need, and it is typically the hardest one of the three array methods to master because though its logic is not complicated, its use cases can be very creative.
That concludes our whirlwind tour of the most commonly used array methods! Practicing these will help you in your day to day work, and making them second nature will really help in interviews and coding challenges.
Array Methods for Selection
Array Methods
Flat
Sometimes we have to deal with arrays within arrays - also called multidimensional arrays. The bigger and more nested multidimensional arrays get, the harder they become to wrap your head around, and accessing information can be a headache. flat is a tool that allows you to undo array nesting to exactly the level you want.
Code Example
In the following code, flat is applied to arrays: nestedArr and moreNested. The first instance with nestedArr, flat takes away the higher level of nesting. But with moreNested we can see when flat takes in an integer parameter, it will flatten 2 levels of nesting. This returns an array without any nesting.
var nestedArr = [1, 2, [3, 4, [5, 6]]];
nestedArr.flat();
console.log(nestedArr)
// expected output: [1, 2, 3, 4, [5, 6]]
var moreNested = [1, 2, [3, 4, [5, 6]]];
moreNested.flat(2);
console.log(moreNested)
// expected output: [1, 2, 3, 4, 5, 6]
Find
Locating unique values in arrays is another indispensable ability. JavaScript find and Includes methods perform similar actions, but their use cases differ. Here are their Mozilla definitions side by side:
Find Method Includes Method find returns the value of the first element in the provided array that satisfies the provided testing function. includes determines whether an array contains a certain value among its entries, returning true or false as appropriate. find is most useful when you are not looking for a specific value. It is best to use find when you want to see if any item in array meets a criteria. In this way, find is a little bit like filter, in that they both run every item in array through a function to determine if the item passes the function’s requirement. But, find is a little bit simpler in that it only passes a single argument (the current value) and returns a single value from the array (the first one to pass the function’s test).
Code Example
The code below shows that if no value in the array is found to meet the criteria, undefined will be the result. Also, if there are multiple items that match the criteria, it does not change the output.
const bestBars = [
'Mos Eisley Cantina',
'Clark`s Bar',
'10 Forward',
'The Restaurant at the End of the Universe',
'The Prancing Pony',
'10 Forward',
]
const test1 = bestBars.find(x => x === 'Quark`s Bar')
const test2 = bestBars.find(x => x === '10 Forward')
console.log(test1) //expected output: undefined
console.log(test2) // expected output: 10 Forward
Include
includes is most useful when are looking for the existence of a specific value. All you have to do is provide the value you are looking for, and includes will return true if it finds it and false if it does not.
It is good to note that neither find nor includes is a good tool to use if you need to know how many times a value is found in an array. To do that, you would have to use map or filter.
Code Example
In the following code, you can see that includes only returns a true or false value. Includes can also be used on strings. `` javascript const bestBars = [ 'Mos Eisley Cantina', 'Clark
s Bar', '10 Forward', 'The Restaurant at the End of the Universe', 'The Prancing Pony', '10 Forward', ]
const test1 = bestBars.includes('Quark`s Bar')
const test2 = bestBars.includes('The Prancing Pony')
console.log(test1) // expected output: false
console.log(test2) // expected output: true
Exercises
// ----------------------------------------------------------
// FLAT EXERCISE
// ----------------------------------------------------------
// This short list of some marvel characters is abitrarily nested. Experiment with the effect of flattening to various depths.
const characters = [
['Starlord', 'Gamora', 'Groot'],
['Dr. Strange', ['Captain America', 'Bucky Barnes'], ['Thor', 'Hulk', ['Loki']], 'Thanos'],
['Iron Man', 'Ultron'],
['Spider Man', ['Venom']],
['Professor X', 'Wolverine', 'Quicksilver', ['Magneto']]
]
const results = characters.flat(5)
// const results = characters.flat(2)
// const results = characters.flat(3)
// And what happens if you go past the max depth of the array?
// const results = characters.flat(5)
console.log(results);
// ----------------------------------------------------------
// FIND EXERCISE
// ----------------------------------------------------------
// Best use cases for FIND are when you want to cast a wider net, because you get to create your own criteria that can be either very specific or more generic.
// Determine whether any of the following have a value that contains the characters 'ABC'
const ids = [
'ADHKE',
'ANFKM',
'QIMVU',
'PQMFU',
'ABCKO',
'IUABC'
]
const string = "ABCKO";
const contains = ids.find(x => x.includes(string))
console.log(contains)
// ----------------------------------------------------------
// INCLUDE EXERCISES
// ----------------------------------------------------------
// 1. It best to use INCLUDES when what the value is does not matter, simply its presence. Imagine the scenario that you are need to check a user's id against a list of admin id's.
const currentUserId = '29nv283bfc0szn16723'
const admins = [
'02398cn7syap0dmbnv0',
'2389sakvjhw8e7f09fv',
'09mxvb82kzjd6v1sfdg',
'9a76zxmsdnv1u622345',
'29nv283bfc0szn16723',
'029834zmnv9jhgfu2ab',
'12mnz09v87bas78fb12',
'098Xc8x76m3nb4aposi'
]
// ----------------------------------------------------------
console.log(admins.find(x => x === currentUserId))
// 2. Checking between lists. Another thing that you might need to do in real life is check items between two arrays. Check if array B has any values that are also in array A. This becomes especially helpful when the values are hard to distinguish visually
const A = [
'02398cn7syap0dmbnv0',
'2389sakvjhw8e7f09fv',
'09mxvb82kzjd6v1sfdg',
'9a76zxmsdnv1u622345',
'29nv283bfc0szn16723',
'029834zmnv9jhgfu2ab',
'12mnz09v87bas78fb12',
'098Xc8x76m3nb4aposi'
]
const B = [
'13xnse8aanv87Hdnfv8',
'2389sakvjhw8e7f09fv',
'12mn0vnZkadfh237LPd',
'1209MNBd8723nvkwejs',
'2389sakvjhw8e7f09fv',
'098LKJnsvijevkwejf6'
]
console.log(B.filter(element => A.includes(element) ) )
ES6 Variables
let
let is like var in that a value can be edited after it has been declared, but the scoping rules are a bit different between the two.
const
const is what we are going to focus on in this course, and right now, we are just going to look at how const does not go far enough to really be considered functional. In the following example, take a close look at how the consts ‘currentBook’ and ‘bookDetails’ behave differently. We can’t edit ‘currentBook’ - as expected - but we CAN edit ‘bookDetails’. Feel free to run and edit the code below to get a feel for what you can and can’t do.
const currentBook = 'The Time Machine'
const bookDetails = {
title: 'The Time Machine',
author: 'H. G. Wells',
totalPages: 84,
currentPage: 42
}
const library = ['Dune', 'Nineteen Eighty-Four', 'Ender`s Game', 'Hyperion', 'Fahrenheit 451']
currentBook = 'Stranger in a Strange Land'
// results in error, can't edit const value
bookDetails = {
title: 'I, Robot',
author: 'Isaac Asimov',
totalPages: 253,
currentPage: 21
}
// results in error, bookDetails is read-only
bookDetails.currentPage = 75
// this works! We can update values within a const object
library = ['Dune', 'Nineteen Eighty-Four']
// results in error, can't redeclare library
library.concat('The Hitchiker`s Guide to the Galaxy')
// this works! We can update items in the array or add to it
// Note that concat is the best non-mutative way to add items to an array
Code Review - Object Freeze
// ----------------------------------------------------------
// OBJECT FREEZE EXAMPLES
// ----------------------------------------------------------
const currentShow = {
title: 'Dr. Who',
seasons: 11,
currentSeason: 4
}
// as a const, we can do this:
currentShow.currentSeason = 5
// expected output: { title: 'Dr. Who', seasons: 11, currentSeasons: 5 }
// but if we freeze the object
Object.freeze(currentShow);
currentShow.currentSeason = 6;
// this would actually cause an error
console.log(currentShow)
// expected output: {title: 'Dr. Who', seasons: 11, currentSeasons: 5 }
// now that it is frozen we can not update the current season of the current show
Keys
This method returns an array of strings of all an object’s property names. This is unusual because most methods we run on objects are more interested in the values.
Be aware though, this won’t always get the results you think. Along with the properties you are aware of in an object, this might also return other properties belonging to the prototype.
Example Code
The following code shows how to use the Object.keys method when applied
const character = {
id: '12mn18udcbv9823',
name: 'Chewbacca',
race: 'Wookie',
planet: 'Kashyyyk',
job: 'First Mate'
};
console.log(Object.keys(character));
// expected output: Array ["id","name","race","planet","job"]
Object Method - Assign
The object assign method copies the properties from a source object to a target object. All properties in the source object that aren’t in the target will be created on the target, and any property both objects share, the values will be updated to match the source object. Here is the object assign Mozilla page. This method is most useful when you want to combine two objects or update one object when it is edited.
let state = {
name: 'Wash',
ship: {
name: 'Serenity',
class: 'Firefly'
}
role: 'Pilot',
favoriteThing: {
item: "Toy",
details: {
type: 'Toy Tyrannosaurus Rex'
}
}
}
const newState = {
name: 'Mal',
role: 'Captain',
favoriteThing: {
item: "Not complicated"
},
history: ["Browncoat sergeant"]
}
state = Object.assign(state, newState);
// Object.assign(state, newState)
console.log(state)
// expected output:
// { name: 'Mal', ship: { name: 'Serenity', class: 'Firefly' },
// role: 'Captain',
// favoriteThing: { item: 'Not complicated' },
// history: [ 'Browncoat sergeant' ] }
Combined exercises
const characters = [
{
name: 'Marvin the Paranoid Android',
role: 'First Mate',
universe: 'Hitchhikers Guide to the Galaxy',
weapon: 'severe depression',
power_level: 1000
},
{
name: 'Jabba the Hut',
role: 'villain',
universe: 'Star Wars',
weapon: 'henchmen',
power_level: 200
},
{
name: 'Zoë Alleyne Washburne',
role: 'First Mate',
universe: 'Firefly',
weapon: 'Winchester Model 1892 rifle',
power_level: 160
},
{
name: 'Peter Venkman',
role: 'Ghostbuster',
universe: 'Ghostbusters',
weapon: 'proton pack',
power_level: 120
},
{
name: 'Kathryn Janeway',
role: 'Captain',
universe: 'Star Trek',
weapon: 'Wit',
power_level: 140
},
{
name: 'Dr. Daniel Jackson',
role: 'Archeologist',
universe: 'Stargate',
weapon: 'Zat gun',
power_level: 120
},
{
name: 'Q',
role: 'God/Eternal',
universe: 'Star Trek',
weapon: 'Whatever he wants',
power_level: 1000
},
{
name: 'Boba Fett',
role: 'Bounty Hunter',
universe: 'Star Wars',
weapon: 'EE-3 carbine rifle',
power_level: 400
},
{
name: 'Yoda',
role: 'Jedi Master',
universe: 'Star Wars',
weapon: 'The Force',
power_level: 900
},
{
name: 'Mal Reynolds',
role: 'Captain',
universe: 'Firefly',
weapon: 'pistol',
power_level: 160
},
{
name: 'Spock',
role: 'First Mate',
universe: 'Star Trek',
weapon: 'Logic',
power_level: 170
},
{
name: 'R2-D2',
role: 'Ship`s Robot',
universe: 'Star Wars',
weapon: 'Data Probe',
power_level: 250
},
{
name: 'Lore',
role: 'Villain',
universe: 'Star Trek',
weapon: 'Intellect',
power_level: 800
},
]
// ----------------------------------------------------------
// COMBINED PRACTICE 1
// ----------------------------------------------------------
// Create an array containing only the names of Captains from all universes.
// Your Code here
const captains = characters.filter(element => element.role === "Captain").map(element => element.name)
console.log(captains)
// expected output: ['Mal Reynolds', 'Kathryn Janeway']
// ----------------------------------------------------------
// COMBINED PRACTICE 2
// ----------------------------------------------------------
// Group all characters by universe in a multidimensional array
// Your Code here
const universeGroups = characters.reduce((acc, curr, i, arr) => {
acc[curr.universe] = acc[curr.universe] === undefined ? [] : acc[curr.universe] // for initally creating an empty array []
acc[curr.universe].push(curr) // push current obejct to corresponding array index
if (i + 1 == arr.length) { // for last element within reducer call return values only
return Object.values(acc)
}
return acc
}, {}
)
// expected output:
console.log(universeGroups)
// [
// [
// { name: 'Marvin the Paranoid Android',
// role: 'First Mate',
// universe: 'Hitchhikers Guide to the Galaxy',
// weapon: 'severe depression',
// power_level: 1000
// }
// ],
// [ { name: 'Jabba the Hut',
// role: 'villain',
// universe: 'Star Wars',
// weapon: 'henchmen',
// power_level: 200 },
// { name: 'Boba Fett',
// role: 'Bounty Hunter',
// universe: 'Star Wars',
// weapon: 'EE-3 carbine rifle',
// power_level: 400 },
// { name: 'Yoda',
// role: 'Jedi Master',
// universe: 'Star Wars',
// weapon: 'The Force',
// power_level: 900 },
// { name: 'R2-D2',
// role: 'Ship`s Robot',
// universe: 'Star Wars',
// weapon: 'Data Probe',
// power_level: 250 } ],
// [ { name: 'Zoë Alleyne Washburne',
// role: 'First Mate',
// universe: 'Firefly',
// weapon: 'Winchester Model 1892 rifle',
// power_level: 160 },
// { name: 'Mal Reynolds',
// role: 'Captain',
// universe: 'Firefly',
// weapon: 'pistol',
// power_level: 160 } ],
// [ { name: 'Peter Venkman',
// role: 'Ghostbuster',
// universe: 'Ghostbusters',
// weapon: 'proton pack',
// power_level: 120 } ],
// [ { name: 'Kathryn Janeway',
// role: 'Captain',
// universe: 'Star Trek',
// weapon: 'Wit',
// power_level: 140 },
// { name: 'Q',
// role: 'God/Eternal',
// universe: 'Star Trek',
// weapon: 'Whatever he wants',
// power_level: 1000 },
// { name: 'Spock',
// role: 'First Mate',
// universe: 'Star Trek',
// weapon: 'Logic',
// power_level: 170 },
// { name: 'Lore',
// role: 'Villain',
// universe: 'Star Trek',
// weapon: 'Intellect',
// power_level: 800 } ],
// [ { name: 'Dr. Daniel Jackson',
// role: 'Archeologist',
// universe: 'Stargate',
// weapon: 'Zat gun',
// power_level: 120 } ] ]
// ----------------------------------------------------------
// COMBINED PRACTICE 3
// ----------------------------------------------------------
// Create an array containing characters' names who are the only character listed in their universe.
// Your Code here
console.log("EXERCISE 3 ##################")
const containtCharacters = characters.reduce((acc, curr, i, arr) => {
acc[curr.universe] = acc[curr.universe] === undefined ? [] : acc[curr.universe] // for initally creating an empty array []
acc[curr.universe].push(curr) // push current obejct to corresponding array index
if (i + 1 == arr.length) { // for last element within reducer call return values only --> reduced to one value/array
return Object.entries(acc)
.filter(([_, element]) => element.length === 1)
.map(([_, element]) => element[0].name)
}
return acc
}, {}
)
console.log(containtCharacters);
// expected output: [ Marvin the Paranoid Android, Peter Venkman, Dr. Daniel Jackson ]
// ----------------------------------------------------------
// COMBINED PRACTICE 4
// ----------------------------------------------------------
// What is the average power level across all characters?
// Your code here
// expected output: 68.71319452795147
const averagePower = characters
.map (element => element.power_level)
.reduce ((acc, element, index, array) => {
if ( index + 1 === array.length)
{
return acc/(index+1)
}
return acc = element + acc;
})
const sum = characters.map (element => element.power_level)
// // expected output: 355.38461538461536
console.log(averagePower);
// expected output: 68.71319452795147
const avgPowerLvl = characters
.map(c => c.power_level)
.reduce((acc, curr, i) => (acc += curr) / i)
console.log('avgPowerLvl:', avgPowerLvl)
Higher-Order Function Intro
Introduction
Higher-Order Functions are not only used in Functional programming but are a central part of Functional programs specifically. Higher-Order Functions (HOFs) allow us to string together many functions. HOFs have slightly different definitions across programming languages, but in JavaScript, you can say that a function is Higher-Order if it:
- Takes in a function as an argument (a callback)
- Returns a function
Often, you will see both of these happen in a single function.
We are already quite familiar with functions that take a callback. Map, Filter, and Reduce are all HOFs. When a function returns another function, it forms a closure around that function, typically to wrap it with extra functionality. HOFs are an important tool for creating more-complex Functional programs in JavaScript.
Code Review
In case you want to play around with the examples from the video, we’ve provided the code snippets below:
// Doubling
const nums = [1, 2, 3, 4, 5]
const doubles = nums.map(x => x * 2)
console.log(doubles)
// expected output: Array [2, 4, 6, 8, 10]
// Equivalent to:
const doubler = function (x) {
return x * 2
}
const doubles2 = nums.map(doubler)
// inside map…
// doubler(1, 0, [1, 2, 3, 4, 5])
console.log(doubles2)
// expected output: Array [2, 4, 6, 8, 10]
Example
weaponsWithNoises = [
{name: 'Phaser', noise: 'bssszzsssss', universe: 'Star Trek'},
{name: 'Blaster', noise: 'Pew Pew', universe: 'Star Wars'},
{name: 'Sonic Screwdriver', noise: 'Pew Pew', universe: 'Dr. Who'},
{name: 'Lightsaber', noise: 'Pew Pew', universe: 'Star Wars'},
{name: 'Noisy Cricket', noise: 'Pew Pew', universe: 'Men in Black'}
]
// solution:
function weaponsFromUniverse(universe) {
const useableWeapons = weaponsWithNoises.filter(w => w.universe == universe)
const useWeapon = (weaponName) => {
const weapon = useableWeapons.find(w => weaponName == w.name)
if (weapon) {
console.log(`used ${weapon.name}: ${weapon.noise}`)
} else {
console.log(`${weaponName} is not a part of the ${universe} universe`)
}
}
return useWeapon
}
// USAGE
const useStarWarsWeapon = weaponsFromUniverse('Star Wars')
useStarWarsWeapon('Blaster') // console logs 'used Blaster: Pew Pew'
useStarWarsWeapon('Noisy Cricket') // console logs 'Noisy Cricket is not a part of the Star Wars universe'
Resources
You can learn more about Higher-Order Functions from Eloquent JavaScript's Chapter on Higher-Order Functions.
Functional Data Manipulations
Working with data is a primary requirement for any programmer, so we are going to practice manipulating data with the methods learned in the last lesson. To make this section as realistic to a job situation as possible, we will practice manipulating real API response data.
var nearEarthObjects = require('./nasa_near_earth_object_API.json');
// The object in the nasa_near_earth_object_API.json is a copy of real API response from the NASA Near-Earth Object API.
// Find the following from the API:
// Total Count ---------------------------------------------
// 1. How many near-earth objects did NASA register for the date of the search? Return the asteroid count.
const elementCount = nearEarthObjects.element_count;
console.log(elementCount)
// Averages ------------------------------------------------
// 2. What was the average absolute magnitude of all the near earth objects in this data set? Return the average absolute_magnitude_h.
const asteroids = Object.values(nearEarthObjects.near_earth_objects).flat();
const averageAbsMagnitude = asteroids
.map(element => element.absolute_magnitude_h)
.reduce((acc, currentValue, index, array) => {
if (index + 1 == array.length) {
return acc / array.length
}
return (acc += currentValue)
})
console.log(averageAbsMagnitude)
// Hint - you can achieve this multiple ways, but the reduce method can be a little-known but cool way to find averages. To do it though, you'll need to use the initial_value argument
// For some extra challenge try using reduce with the initial setting argument. To learn more about it, take a look at this page: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
// Hazardous -----------------------------------------------
// 3. A list of all objects (their id, name, max size in miles, and closest approach in miles) that are labeled potentially hazardous
//console.log(asteroids)
const hazardous = asteroids
.filter(element => (element.is_potentially_hazardous_asteroid === true))
.map(element => {
return {
id: element.id,
name: element.name,
max_size: element.estimated_diameter.miles.estimated_diameter_max,
closestApproach: element.close_approach_data[0].miss_distance.miles
}
})
console.log(hazardous)
// Too Close for Comfort -----------------------------------
// 4. A list of all objects (their id, name, max size in miles, and closest approach in miles) that have a miss_distance of less than 900,000 miles
const tooClose = asteroids
.filter(element => (element.close_approach_data[0].miss_distance.miles < 900000))
.map(element => {
return {
id: element.id,
name: element.name,
max_size: element.estimated_diameter.miles.estimated_diameter_max,
closestApproach: element.close_approach_data[0].miss_distance.miles
}
})
console.log(tooClose)
// Alert ---------------------------------------------------
// 5. Of all the near-earth objects for this date, find the time that the asteroid with the nearest miss will be closest to earth.
const alert = asteroids
.map(element => {
const time = element.close_approach_data[0].close_approach_date_full;
const miss_distance = element.close_approach_data[0].miss_distance.miles;
return {
miss_distance: miss_distance,
time: time
}
})
.reduce((closest, current) => {
const currDist = parseFloat(current.miss_distance);
const closestDist = parseFloat(closest.miss_distance);
if (currDist < closestDist)
{
return current;
}
else
{
return closest;
}
})
console.log(alert)
DOM manipulations
Functional DOM Manipulations
Dealing with the DOM is inherently not Functional. In fact, working with the DOM often requires relying on “side effects” and other practices that go fully against the Functional concepts we learned at the beginning of this course. We saw in the last section that manipulating data according to Functional concepts is totally possible and often is to our advantage, but things are going to get trickier trying to work with the DOM in a Functional way. In this section, we are going to create our own Functional, React-inspired way to create and update DOM elements. The goal is that by doing this, you will see where the Functional concepts come into play. It is important to note that this course does not teach any React and no React knowledge is needed to proceed with the lesson. It is enough to know that React takes a Functional styled approach to Front End web development, so some of the strategies we will use are similar.
Examples on maniuplating the DOM with functional programming
Render Method - Exercise Solution
// Here is the code from the example text
const root = document.getElementById('root')
const Welcome = () => {
return `Welcome to my JavaScript Program!`
}
const App = () => {
return `
<h1>${Welcome()}<h1>
<div> I EXIST! </div>
`
}
const render = root => {
root.innerHTML = App()
}
render(root)
// Add a new Menu component that takes in a show argument which is either true or false
// Show this content if show is true:
{/* <nav>
<ul>
<li>About Us</li>
<li>Contact Us</li>
<li>Login</li>
</ul>
</nav> */}
// and this content if show is false:
{/ *<nav>Menu</nav>* / }
const Menu = (show) => {
if(show) {
return (`
<nav>
<ul>
<li>About Us</li>
<li>Contact Us</li>
<li>Login</li>
</ul>
</nav>
`)
}
return `<nav>Menu</nav>`
}
Application State - Exercise Solution
We have seen that we can create elements in our JavaScript program and make them appear in the DOM, but now how do we hold onto values, take in data and do all the other things necessary for an interactive app? That is the focus of this section. We need a place to hold information. Our app is likely going to collect information from users or receive it from an API, and we need an organized way to handle data from any source. Functional Programming stays away from creating a global state, but in a real life web application, sticking to that rule proves difficult and overly complicated to do. Instead of no global state, what we can do is create just one place for all that global information to live. We will create an immutable object to store data - Functional Programming has no rules against that - and we will use this immutable object as the single source of truth for our application’s data.
const store = {
user: {
first_name: ‘John’,
last_name: ‘Doe’
}
}
Store is a generic term for an object that stores application data. That will be the purpose of this immutable object, so we named it store and it can now be passed as an argument into our functions.
const render = (root, state) => {
root.innerHTML = App(state)
}
const Welcome = (name) => {
return `Welcome, ${name} to my JavaScript Program!`
}
const App = (state) => {
return `
<h1>${Welcome(state.user.first_name)}<h1>
<div> I EXIST! </div>
`
}
render(root, store)
Here we have taken the code that generated the DOM elements in the last section and passed our new store object to it so that data is accessible to our component functions. One more thing, if you have recreated these two sections on your own computer and tried to see the results in your browser, there was likely a problem and nothing showed up on the screen except what was in your index.html file. Why’s that? Because we are missing one function that tells the JavaScript when to run. You might have seen this function from other Javascript programs for the browser. We need to wait for the load event to make sure that our JavaScript doesn’t run before the DOM element ‘root’ exists.
window.addEventListener('load', () => {
render(root, store)
})
Now we are telling our JavaScript to wait until the load event has finished in the browser, which means it's safe to create the rest of our layout.
// Given
let store = {
user: {
first_name: 'John',
last_name: 'Doe'
}
}
const render = (root, state) => {
root.innerHTML = App(state)
}
const Welcome = (name) => {
return `Welcome, ${name} to my JavaScript Program!`
}
const App = (state) => {
return `
<h1>${Welcome(state.user.first_name)}<h1>
<div> I EXIST! </div>
`
}
window.addEventListener('load', () => {
render(root, store)
})
// But what if state changes at some point in our app?
// Using the object methods we have learned so far in this course,
// create a function called updateStore that takes in the old state, the new state, and updates the old state with any new information
// This won't be a pure function, instead of a return, call the render method again
const updateStore = (store, newState) => {
store = Object.assign(store, newState)
render(root, store)
}
Summary
There isn’t such a thing as truly “Functional” DOM manipulations, but we have now seen how to isolate the parts of our logic that can’t be functional ( like the render method from the exercise) so that we are free to write the main logic in a Functional style.
Bonus fact: If you are familiar with React, this is exactly how React handles non-Functional DOM manipulations. Next time you spin up a Create React App project - take a look in the index.html file and first render function.
Going Further with Functional JS Intro
Introduction
We’re in the home stretch! We’ve finished our investigation of the Javascript tools we have to write Functional programs, and we’ve seen first hand how those tools can be used to create an interactive program without the use of any external libraries. Now it's time to get a wider view of all that’s possible with Functional Programming and some of the awesome libraries built on top of these powerful concepts.
In this lesson, we will do a very high-level overview of a few of the most common Functional libraries for JavaScript - ImmutableJS, Ramda, RxJS, and Redux. The point of this lesson is not to teach you all there is to know about these awesome tools, but only to show three real life uses of the Functional concepts we have learned. Here and there I will stop to explain a new concept or look at some code, but the sole focus of this section will be to highlight how these tools put different Functional practices to good use. None of the lessons in this section should be seen as a tutorial on how to set up or use these libraries in an application. To go into more detail for any of these technologies would require another course of its own, so instead I will provide resources for where you can continue learning about each library on your own.
ImmutableJS
Introduction
Hopefully you’re all fired up to make use of Persistent Data Structures in your next JavaScript program, but writing all that functionality from scratch would be time consuming. Instead, we can use ImmutableJS, which is a library that gives us Persistent Data Structures in JavaScript. ImmutableJS has done the work of writing the JavaScript functionality so that when you create an object, it can look pretty much like a normal JavaScript object, but under the hood it will behave like a persistent data structure. This library is not solely a Functional library; you could actually use it in all the JavaScript you write if you wanted. The creators of Immutable have gone behind the scenes to create some very smart ways of handling data structures specific to JavaScript.
To give you a clearer picture of what we’re talking about, let's take a look at some of the Immutable syntax.
First, because Immutable is an external library, we have to add it to our project. We can do that one of two ways: via a script tag in an HTML page, or via a node module. Choose whichever one is more convenient for your application. For this course, we are going to use the browser script tag implementation. Here is an example of how ImmutableJS is implemented.
Immutable.js Basic Syntax
Now let's take a look at some syntax. We are going to take a quick tour of what it looks like to use Immutable, then once we have a feel for the syntax we’ll go back and take a look at how this uses persistent data structures under the hood.
// Getting and Setting values using Immutable module
// Immutable objects are called ‘maps’
const Immutable = require('immutable'); // import immutable module
// this is how we declare an immutable Object
const map1 = Immutable.Map({ a: 1, b: 2, c: 3 });
// Here is an important part. We can’t update map1, so instead we have to make the change on map2.
const map2 = map1.set('b', 50);
// Because of this, the old state of map1 is still available
map1.get('b'); // 2
// And map2 shows our update.
map2.get('b'); // 50
console.log(map2.get('b'))
// equality check
map1.equals(map2); // false
console.log(map1.equals(map2))
// Use with ordinary objects
const state1 = Immutable.Map({ a: 1, b: 2, c: 3, d: 4 });
const state2 = Immutable.Map({ c: 10, a: 20, t: 30 });
const obj = { d: 100, o: 200, g: 300 };
const map3 = state1.merge(state2, obj);
console.log(map3.toJS()) // toJS() is a helper method to convert immutable object into javascript dictionary
// If you open the returned object, you can see that it isn't an ordinary object
// you'll find all the properties under "entries"
// Use with ordinary arrays
// An immutable array is declared as a `List` like this:
const numbers = Immutable.List([1, 2, 3]);
//But Immutable can also work with plain JS arrays
const otherNumbers = [4, 5, 6]
Implementing ImmutableJS
There are many options when it comes to implementing ImmutableJS. You can install it as an npm module, grab it with a script in the browser, or use it in a Typescript project. The recommended way is to use the npm module. However, for the workspace environment that we are using in this course, we are going to use the browser script just to test it out. For use in the course project though, you should use the npm module.
WATCH OUT! One important thing to note is that nested objects have to be declared as Immutable maps just like top level ones. Otherwise, they will be stored as normal JS objects, which Immutable handles differently. You can see the difference below.
// Nest 1
const currentShow = Immutable.Map({
title: 'Dr. Who',
seasons: 11,
currentSeason: 4,
characters: {
main: 'The Doctor',
supporting: ['Dalek1']
}
})
// Nest 2
const currentShow = Immutable.Map({
title: 'Dr. Who',
seasons: 11,
currentSeason: 4,
characters: Immutable.Map({
main: 'The Doctor',
supporting: List(['Dalek1'])
})
})
Nest 1 is not the same as Nest 2. The main difference is that the nested object characters is declared with Immutable.Map in Nest 2. characters in Nest 1 will not be immutable while characters in Nest 2 will be.
Summary Immutable makes it possible for us to write even better Functional code - or just better code in general - without being a big task to learn, which makes it a big win to me. If you want to look further into the how’s and the why's of Persistent Data Structures and ImmutableJS, a great place to start is their well-written documentation.
RxJS and Ramda
Introduction
Two more functional libraries we will take a quick look at are RxJS and Ramda. Each can help you write Functional code more efficiently, or keep you honest about not bending Functional rules. Basically used in angular.
Code Review
// A function runs once and returns one set of information
// An observable is like a function that runs for a period of time and can return many sets of information - a stream of information
// An observable returns a stream of information
// An observer (here, observable.subscribe) is how we ‘listen’ or subscribe to data being returned by an observable.
const observable = new Rx.Observable(subscriber => {
subscriber.next(1);
subscriber.next(2); // "return" another value
subscriber.next(3); // "return" another value
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
});
console.log('just before subscribe');
observable.subscribe({
next(x) { console.log('got value ' + x); },
error(err) { console.error('something wrong occurred: ' + err); },
complete() { console.log('done'); }
});
console.log('just after subscribe');
// the word 'next' is a special RxJS observable word that allows the observable to "return" multiple things over a period of time
// There are three types of values an Observable Execution can deliver:
// "Next" notification: sends a value such as a Number, a String, an Object, etc.
// "Error" notification: sends a JavaScript Error or exception.
// "Complete" notification: does not send a value.
// "Next" notifications are the most important and most common type: they represent actual data being delivered to a subscriber.
// "Error" and "Complete" notifications may happen only once during the Observable Execution, and there can only be either one of them.
Ramda
Ramda is a library of functions that help JavaScript developers write better Functional code. Where RxJS is a whole new way to think about and interact with data streams and events, Ramda’s goal is to do an excellent job of providing JavaScript developers with truly Functional tools, and a lot of developers think they have done a really good job at that.
Some cool things about Ramda:
- Each function is self-contained, pure, side effect free, and does not mutate anything
- Each function is written in JavaScript, so with the amount of JavaScript you know now, you can go to the source code and be able to understand how they built their function
Some cool functions in Ramda:
- If you felt robbed when you learned that push, pop, etc. weren’t allowed in Functional programming - Ramda gives them back to you! Their array Update, Tail, Take, and TakeLast are all great options.
- Ramda even takes it one step further and allows you to leave specific values out of an array using the function Without(opens in a new tab)
- Remember how we struggled with Object.assign not being functional? Well with Ramda’s Merge(opens in a new tab) function - it can be. Merge does exactly the same as Object.assign, except it returns a new object.
- Ramda Pluck(opens in a new tab) is another great example. Knowing that developers often work with long arrays of objects, Pluck allows you to choose one property those objects share and will return a new array of just those values.
In the edge cases where you really want a functional method, but the pure JavaScript doesn’t support it, Ramda will be a great resource. So if you end up writing more Functional code, keep Ramda in mind as a great option for adding more Functional utility without changing your workflow.
Summary
I hope that at least one of these libraries has piqued your interest to go and learn beyond this course. All three are fantastic tools for expanding your Functional Javascript capabilities.
Functional Redux
Introduction
Reducer Analogy The actions of the reducer can be compared to a teller at a bank. As a bank patron (event) you want the teller to do something to your account such as withdraw, deposit, or create an account (actions) and the teller (reducer) is able to make changes to the vault (store) where your money (data) is stored.
Pure Functions in Redux
Redux is a library typically imported into larger applications to manage and represent state in your application with a single Javascript object. Redux uses pure functions called Reducers to enact updates on the state object which is represented by the immutable store object. This is straight from their documentation:
“Reducers are just pure functions that take the previous state and an action, and return the next state.”
Oftentimes, reducers are written as functions that contain case statements. Take a look at this example reducer from their docs:
import { createStore, combineReducers } from 'redux'
const SET_NAME = 'SET_NAME'
const SET_AGE = 'SET_AGE'
function user(state = initialUserState, action) {
switch (action.type) {
case SET_NAME: {
return {
...state,
name: action.name,
}
}
case SET_AGE: {
return {
...state,
age: action.age,
}
}
}
}
const ADD_TODO = 'ADD_TODO'
const COMPLETE_TODO = 'COMPLETE_TODO'
const initialState = {
todos: []
}
function todos(state = initialState, action) {
switch (action.type) {
case ADD_TODO: {
const { todos } = state
return {
...state,
todos: [...todos, action.todo],
}
}
case COMPLETE_TODO: {
const { todos } = state
const index = todos.find(todo => todo.id == action.id)
toods[index].comleted = true
return {
...state,
todos,
}
}
default:
return state
}
}
const reducers = combineReducers({
todos,
user,
})
const store = createStore(reducers)
The ellipses before state is called “spreading” the object, which means to return the original value for state, except where it is different from the portion of state that was updated, in this case text and completed. Redux is a great example of how you can accomplish a lot by making good use of simple concepts.
Functional Highlights of Redux Redux is a big topic and this is not a Redux tutorial. What is important to this course is that Redux has found great success among developers because it uses Functional programming practices and is a great example of these power concepts being used in the real world.
Here are the Functional highlights of the Redux library:
- A read-only -- or immutable -- state. Reducers don’t update state; they return a brand new object.
- Pure Function reducers to handle Actions. This means that no actions in our entire app can be affected by Side Effects. This is a huge reason Redux is so effective.
- This careful Functional flow of data from event to store means that the same action, if called twice, will have the same effect on the whole application.
Example
import { DAMAGE_SHIELD, REPAIR_SHIELD } from './actions'
const initialState = {
shieldLevel: 100,
}
const shieldsReducer = (state = initialState, action) => {
switch (action.type) {
case DAMAGE_SHIELD:
const { amount } = action
let { shieldLevel } = state
shieldLevel -= amount
return {
...state,
shieldLevel,
}
case REPAIR_SHIELD:
const { amount } = action
let { shieldLevel } = state
shieldLevel += amount
return {
...state,
shieldLevel,
}
}
}
Summary
Immutable, Ramda, RxJS, and Redux, all of these are valuable Functional tools that you might come across in a job and which will help you greatly as you build web apps that are capable of dealing with complex logic. This lesson was to give you a lay of the land as to the options for Functional programming in Javascript, and I hope the resources will serve you well if you choose to tackle any in more depth.
Course Recap
As part of the Intermediate Javascript Nanodegree program, my hope for this course is that it has challenged your skills on two fronts - your knowledge and comfort with advanced syntax, and your ability to judge and design advanced programs. Both of these together are required for you to advance to a mid and senior-level developer.
The sections on ES6 syntax, Higher-Order Functions, and data manipulations and associated challenges were designed to challenge your ability to wield advanced and modern syntax to accomplish your goals.
The section on paradigms and the introduction to Lambda Calculus were intended to challenge how you think about your programs. Growing your ability to reason about, critique, and make decisions about the overall design of your programs is vital to advancing as a developer -- it is equally as important as mastering syntax.
I sincerely hope that this course has been able to challenge you in both of these areas! Thank you so much for joining me and I wish you the best of luck in the project and the next course!