Functional JavaScript
- Don’t mutate objects
- Don’t cause side effects - Your callback can only modify the new value you’re returning from that callback.
An Introduction to Functional JavaScript
Functional superscripts over Javascript
- PureScript
- Sanctuary - Refuge from unsafe JavaScript
- Specification for interoperability of common algebraic structures in JavaScript
Immutability in JS
Don’t mutate an object, but create a new one with the change, i.e. create a new object in memory with a new reference
-
Vanilla JS
// objects var yourCarRepainted = Object.assign({}, yourCar, { color: 'red' }); // array var changedList = [].concat(list); - Or use libraries like immutable.js
Array Functions (collection functions)
Array operations without having to write loops explicitly. This is a great article about map(), reduce() and filter()
Examples for most common use cases:
-
The
reduce()callback always expects the new total value to be returned, even if that value is an array, and even if it’s the same one as before.reduce()
[0, 1, 2, 3, 4].reduce((prev, curr) => prev + curr);or with initial value
{}fortotal:[0, 1, 2, 3, 4].reduce((total, curr) => total + curr, 0);Add stuff to an array with reduce:
var newNumbers = numbers.reduce(function (newArray, number) { newArray.push(number); if (number % 2 == 0) { /* Add it a second time. */ newArray.push(number); } return newArray; /* This is important! */ }, []);Nice code example of
reduceusage:Returns an object of the form
{ processId1: [assessment1, assessment2], processId2: [assessment3, assessment4], // ... }const assessmentsByProcess = useQuery(getAssessments, { orderBy: { id: "asc" }, })[0].assessments.reduce((accum, curr) => { // if there is already at least one assessment with a certain processId, add assessments with the same processId to it. if (accum[curr.processId]) { return { ...accum, [curr.processId]: [...accum[curr.processId], curr], } } // An assessment with a certain project id was found the first time -> just append it to the object return { ...accum, [curr.processId]: [curr], } }, {})reduceRight():reduce()but from right to left instead of left to right.
-
map() vs. forEach() vs. every()
map(): Apply function to each element in Array and return new array with altered values.
[0, 1, 2, 3, 4].map( (value) => value + 2 );forEach():
Loop through array. Callback function does not return anything as
map()does.Loop through object:
var numbers = {one: 1, two: 2, three: 3, four: 4}; Object.keys(numbers).forEach(function(key){ var value = numbers[key]; doSomethingWith(value); /* For example, key == "one" and value == 1 */ });every():
-
filter() Filter the array according to the provided filter function.
[0, 1, 2, 3, 4].filter((value) => value > 0); -
Sort the current array according to a sort callback function which returns a boolean
[0, 1, 2, 3, 4].sort((a, b) => a < b); -
myArray.join(','); -
-
slice():- Pass by value, i.e. a new copy of the array is created instead of just a new reference.
-
Shallow copy: It copies by value if it’s an array of primitive types, like Strings or Integers. If it’s an array of arrays or array of objects, the elements of the array will only by copied by reference, not by value.
newArray = myArray.slice();
-
- every()
- fill()
Stack, Queue
push()Push new element to end of stack/queuepop()Pop last object in stackshift()Enqueue first object in queue
Immutable.js
Elements
Map()
-
mergeDeep()Example:var x = Immutable.fromJS({ a: { x: 10, y: 10 }, b: { x: 20, y: 50 } }); var y = Immutable.fromJS({ a: { x: 2 }, b: { y: 5 }, c: { z: 3 } }); x.mergeDeep(y); // {a: { x: 2, y: 10 }, b: { x: 20, y: 5 }, c: { z: 3 } } setIn(),getIn()
Lazy Seq
Seq is lazy — Seq does as little work as necessary to respond to any method call.
Efficient chaining:
var seq = Immutable.Map({ a: 1, b: 1, c: 1 }).toSeq();
seq
.flip()
.map((key) => key.toUpperCase())
.flip()
.toObject();
// Map { A: 1, B: 1, C: 1 }Immutable.Range(1, Infinity)
.skip(1000)
.map((n) => -n)
.filter((n) => n % 2 === 0)
.take(2)
.reduce((r, n) => r * n, 1);
// 1006008Batching mutations
var list1 = Immutable.List.of(1, 2, 3);
var list2 = list1.withMutations(function (list) {
list.push(4).push(5).push(6);
});
assert(list1.size === 3);
assert(list2.size === 6);Records
Records are immutable. Records inherit all Immutable.Map functions such as set, setIn, merge etc, and each of these return a new instance of the record.
They combine the benefits of ImmutableJS’ Map() with normal JS objects. They have standard accessors (post.name vs post.get('name'))
import { Record, Map } from 'immutable';
export const Post = new Record({
id: undefined,
title: '',
content: '',
author: undefined
comments: new Map()
});const post = new Post({ title: 'foo', content: 'misc' });
const edited = post.set('title', 'bar');const data = new Map({
somePost: new Post({ title: 'some post' }),
});
console.log(data.getIn(['somePost', 'title'])); // === 'some post';Extend an immutable record to e.g. create read-only properties:
class StateRecord extends Record({
names: List([]),
isLoading: false,
}) {
get is_empty() {
return StateRecord.names.size === 0;
}
}.withMutations() only works with setters, not getters.
const newMap = map.withMutations(
mutable_map => mutable_map.set('a', 1).set('b', 2);
);Redux
Have a function to to create immutableReducers and custom combineImmutableReducers util function and custom createStore.
Currying and partial function application
Referential transparency
Point 1 in this article explains referential transparency.
Idempotence
Point 2 in this article explains idempotence.
Discuss on Twitter ● Improve this article: Edit on GitHub