Salcescu, Cristian - Functional Programming in JavaScript (Functional Programming With JavaScript and React Book 4) (2020)
Salcescu, Cristian - Functional Programming in JavaScript (Functional Programming With JavaScript and React Book 4) (2020)
Cristian Salcescu
Primitives
Methods on Primitives
Immutability
Variables
Recap
Chapter 02: Immutable Data Objects
Objects
Immutability
Freezing
Linting
Working with Immutable Objects
Recap
Chapter 03: Functions
Function Declarations
Function Expressions
Arrow Function Expressions
Arity
Pure Functions
Recap
Chapter 04: Arrays
Filtering
Mapping
Aggregating
Sorting
Searching
forEach
Impure Operations
Changing Arrays
Recap
Chapter 05: First-Class Functions
First-Class Functions
Higher-Order Functions
Closures
Recap
Chapter 06: Statements
Expressions
Statement Declaration
Conditional Statements
Loop Statements
Disruptive Statements
Recap
Chapter 07: Recursion
Recursive Factorial
Tail-Call Optimization
Stack Overflow Error
Trampoline
Traversing a Tree
Recap
Chapter 08: Pipelines and Currying
Pipelines
Chaining
Currying
Reduce Pipeline
Recap
Chapter 09: Functors
Functors
Functor Laws
Category Theory
Functors in Practice
Arrays
Recap
Chapter 10: Monads
Flat Mapping
Monads
Monad Laws
Alternative Creation and Naming
Arrays
Recap
Chapter 11: Immutable Collections
List
Map
Recap
Chapter 12: Lazy Transformations
Transducers
Sequence
Range
Recap
Chapter 13: Generators
Finite Generators
toArray
forEach
take
Custom Generators
Recap
Chapter 14: Promises
Creating Promises
Success Handling
Error Handling
Chaining
Asynchronous Operations
Functor Laws
Monad Laws
Recap
Chapter 15: Observables
Creation Operators
Pipeable Operators
Observers
Subscriptions
Higher-Order Observables
Flattening Operators
Combining Observables
Functor Laws
Monad Laws
Recap
Chapter 16: Elm Architecture
Elm Architecture
Counter with Elm
Counter with React and Redux
Random Number with Elm
Random Number with React and Redux
Recap
Chapter 17: Functional Paradigm
Purity
Immutability
Pipelines
Loops
Naming
Final Thoughts
What’s next?
Introduction
Functional programming is becoming more popular and this
book aims to bring clarity on how to use JavaScript as a
functional language.
The package.json file stores all the node packages used in the
project. These packages are the application dependencies.
The application can be shared with other developers without
sharing all the node packages. Installing all the packages
defined in the package.json file can be done using the npm
install command.
{
"plugins": [
"functional"
],
"rules": {
"functional/no-this-expression": "error"
}
}
Here is how you can run linting on all files inside the src
folder.
npx eslint src In an application created with Create React App
we can force linting before starting the development server by
prepending the npx eslint src command followed by && .
"scripts": {
"start": "npx eslint src && react-scripts start"
}
Using && means that things on both sides of the && must
evaluate to true . If the command on the left side fails the
command on the right side will not execute, so in our case,
the development server won’t start.
IDE
For code editing, we need an Integrated Development
Environment, IDE in short.
I am going to use Visual Studio Code but feel free to use any
editor you prefer.
To run the code inside the filter.js file for example, and
check the results, we can just use the following command.
npx babel-node filter.js For instant linting feedback we can
install the ESLint extension for VSCode . Linting allows us to
work in a better subset of JavaScript.
Chapter 01: Primitives
We start our learning journey by looking at the first kind of
values available in the language.
console.log(typeof(1));
//"number"
console.log(typeof(''));
//"string"
console.log(typeof(true));
//"boolean"
"text"
'text'
`text`
'ABCDEFG'.length
//7
'ABCDEFG'.type = 'letters';
//Cannot create property 'type' on string 'ABCDEFG'
The only primitives that are not treated as objects are null
and undefined . Trying to call a method on them will throw an
error.
null.toString();
//Cannot read property 'toString' of null Immutability An
immutable value is a value that once created cannot be changed.
fruit.name = 'Orange'
//{name : 'Orange'}
fruit.name = 'Orange'
// {name : 'Orange'}
fruit.name = 'Orange'
// Cannot assign to read only property 'name' of object The
only limitation is that Object.freeze does shallow freezing. It
affects the immediate properties of the object, the ones at the
first level in the tree. If for example one of these properties
contains another object, that object will not be affected. Look
at the next example.
"plugins": [
"functional"
],
"rules": {
"functional/immutable-data": "error"
}
counter.value = 2;
delete counter.value;
Object.assign(counter, { value: 2 });
const newProduct = {
...product,
quantity: 2
};
const product = {
name: 'Apple',
quantity: 1
});
const newProduct = {
...product,
type: 'fruit'
};
const {
quantity,
...newProduct
} = product;
console.log(newProduct);
//{name: "Apple"}
const n = 1;
console.log(increment(n));
//2
console.log(increment(n, 2));
//3
console.log(roundNumber(number));
//1.77
Array.isArray(arr);
//true Arrays have a set of methods for data transformations.
function isEven(n) {
return n % 2 == 0;
}
console.log(strategyGames);
//[ { title: 'Starcraft', genre: 'RTS' },
// { title: 'Command and Conquer', genre: 'RTS' },
// { title: 'Heroes of Might and Magic', genre: 'TBS' } ]
console.log(htmlRows);
// [ '<div>Starcraft</div>',
// '<div>Command and Conquer</div>',
// '<div>Heroes of Might and Magic</div>',
// '<div>World of Warcraft</div>' ]
function add(total, n) {
return total + n;
}
Let’s get back to the game objects array and compute the
numbers of games for each genre.
const games = [
{ title: 'Starcraft', genre: 'RTS' },
{ title: 'Command and Conquer', genre: 'RTS' },
{ title: 'Heroes of Might and Magic', genre: 'TBS' },
{ title: 'World of Warcraft', genre : 'MMORPG'}
];
console.log(gamesByGenreCounts);
//{ RTS: 2, TBS: 1, MMORPG: 1 }
function asc(a, b) {
if(a === b){
return 0
} else {
if (a < b){
return -1;
} else {
return 1;
}
}
}
The sort method does the sort using the compare function.
function isStrategy(game){
const strategyGenres = ['RTS', 'RTT', 'TBS', 'TBT'];
return strategyGenres.includes(game.genre);
}
function log(value){
console.log(value);
}
numbers
.forEach(log);
//1
//2
//3
arr.push(4);
arr.pop();
arr.unshift(0);
arr.shift();
//Modifying an array is not allowed Changing Arrays Next, look
at how we can do the basic add, edit, and remove operations on
an immutable array. Consider the array of game objects.
const games = [
{id: 1, title: 'WarCraft'},
{id: 2, title: 'X-COM: UFO Defense' }
]
console.log(newGames);
//[ { id: 1, title: 'WarCraft' },
// { id: 2, title: 'X-COM: UFO Defense' },
// { id: 3, title: 'The Settlers' } ]
We can use map for this. The mapping function returns the
new value for the specified index otherwise it returns the
existing value.
console.log(newGames);
//[ { id: 1, title: 'WarCraft 2' },
// { id: 2, title: 'X-COM: UFO Defense' } ]
console.log(newGames);
//[ { id: 2, title: 'X-COM: UFO Defense' } ]
function toLowerCase(text){
return text.toLowerCase();
}
function trim(text){
return text.trim();
}
const functions = [
toUpperCase,
toLowerCase,
trim
];
doOperation(sum, 2, 3);
//5
doOperation(multiply, 2, 3);
//6
multiply.call(null, 2, 3);
//6
All the array methods like filter , map , reduce , sort taking
functions as inputs are higher-order functions.
function hasId(id){
return function(element){
return element.id === id;
}
}
console.log(objects.find(hasId(1)));
//{ id: 1 }
function by(name){
return function(a, b){
return a[name].localeCompare(b[name]);
}
}
courses
.slice().sort(by('author')) by is a higher-order function. It
allows us to sort the list using any of the object’s property
that is a string.
Closures Functions can be defined inside other
functions. Besides having access to its
parameters and variables, the inner function
has access to parameters and variables from
the outer functions.
Closure is the ability of an inner function to access variables
from the outer function.
function logValue(){
console.log(value);
}
logValue();
}
run();
//1
console.log(count());
//1
console.log(count());
//2
console.log(count());
//3
multiply(2, 3) is an expression.
const n = 3;
console.log(isEven);
//false Below is an example of a function that returns null
when no product object is provided, returns the price when it
is available otherwise returns the lastPrice .
function getPrice(product){
return product
? product.price
? product.price
: product.lastPrice
: null
}
console.log(getPrice(null));
//null
console.log(getPrice({lastPrice: 10}));
//10
console.log(getPrice({price:5, lastPrice: 10}));
//5
function doAction(actionName){
switch(actionName){
case "increment":
number = increment(number);
break;
case "decrement":
number = decrement(number);
break;
}
}
let number = 0;
doAction('increment')
console.log(number);
//1
const number = 0;
const newNumber = doAction(number, 'increment');
console.log(newNumber);
//1
function decrement(x){
return x - 1;
}
const actionMap = {
increment,
decrement
}
const number = 0;
const newNumber = doAction(number, 'increment');
console.log(number);
//1
// eslint-disable-next-line functional/no-loop-statement
while(i < 10){
console.log(i);
i = i + 1;
}
Both break and continue are similar to the goto statement and
we are not going to use them. Instead, we will use array
methods like find , every , or some .
const id = 1;
let product;
for(let i=0; i<products.length; i++){
if(products[i].id === id){
product = products[i];
break;
}
}
console.log(product);
//{id:1, name: 'apple'}
const words = [
'ability',
'calculate',
'calendar',
'double',
'door'
];
console.log(word);
}
function factorial(n) {
let result = 1;
let i = 2;
while(i <= n){
result = result * i;
i = i + 1;
}
return result;
}
console.log(factorial(5));
//i=2, result=2
//i=3, result=6
//i=4, result=24
//i=5, result=120
//120
console.log(factorial(5));
//120
The factorial function calls itself until it has the result for n
being 0 . Then it resumes the execution and computes the
result for n equal to 1 , 2 , 3 , 4 and finally 5 .
console.log(factorial(5));
//120
return (n === 0)
? result
: factorial(n - 1, n * result);
}
console.log(factorial(5));
//n=5, result=1
//n=4, result=5
//n=3, result=20
//n=2, result=60
//n=1, result=120
//n=0, result=120
//120
return total;
}
console.log(sumAll(10000));
//50005000
console.log(sumAll(5));
//15
return result;
}
}
console.log(_sumAll(10000));
//50005000
const tree = {
value: 0,
checked: false,
children: [{
value: 1,
checked: true,
children: [{
value: 11,
checked: true,
children: null
}]
}, {
value: 2,
checked: false,
children: [{
value: 22,
checked: true,
children: null
},{
value: 23,
checked: true,
children: null
}]
}]
}
function getTopSelection(node){
return node.checked
? [node.value]
: node.children !== null
? node.children.reduce(toChildTopSelection, [])
: []
}
getTopSelection(tree);
//[1, 22, 23]
function shortenText(text) {
return text.substring(0, 8).trim();
}
const shortText =
shortenText(capitalize("this is a long text"));
console.log(shortText);
//"This is"
console.log(shortText);
//"This is"
function isEur(debt){
return debt.currency === "EUR";
}
function toDebtView(debt){
return {
...debt,
priority : getPriority(debt)
}
}
function getPriority(debt){
const days = debt.daysSinceLastPayment;
return days > 90
? 1
: days > 60
? 2
: 3;
}
debts
.filter(isEur)
.map(logIdentity)
.map(toDebtView)
.map(logIdentity); Currying Currying is a technique for
changing a function expecting n arguments into a series of n
functions each expecting one argument.
words
.filter(w => startsWith(w, 'a'))
.forEach(console.log); Now, look at the same code refactored
using currying.
function startsWith(term){
return function(word){
return word.startsWith(term);
}
}
words
.filter(startsWith('a'))
.forEach(console.log); startsWith is a curried function.
Instead of taking two parameters, it takes only one and returns
a function accepting the second parameter. Notice also that the
curried version has the parameters in the reverse order
compared to the initial version taking two parameters.
function add(total, n) {
return total + n;
}
function describeTemperature(temperature){
return temperature < 0
? "Freezing"
: temperature < 15
? "Cold"
: temperature < 28
? "Warm"
: "Hot";
}
console.log(celsius);
//26.85
console.log(description)
//"Warm"
As you can see, the point of this piece of code is to apply
two changes to a single value. It starts with the Kelvin
temperature and applies the toCelsius transformation, then
on the result, it applies the describeTemperature function.
function map(f){
const newValue = f(value);
return F(newValue);
}
return {
map
}
}
F creates
an object with the map method wrapping a value .
map takes
a transformation function, applies it to the
wrapped value, and returns a new object wrapping the new
value.
F is called a factory function. F returns an object containing
a closure, map , accessing the value variable from its parent.
function double(n){
return n * 2
}
F(1)
.map(increment)
.map(double)
//F(4)
F(1)
.map(n => double(increment(n)))
//F(4) Category Theory A category is a collection of values
that are linked by arrows.
function logIdentity(value){
console.log(value);
return value;
}
F(300)
.map(toCelsius)
.map(logIdentity) //26.85
.map(describeTemperature)
.map(logIdentity) //"Warm"
[1, 2, 3]
.map(x => double(increment(x)))
.map(logIdentity);
//4
//6
//8
[300]
.map(x => describeTemperature(toCelsius(x)))
.map(logIdentity);
//"Warm"
console.log(arr);
//[ 'RASPBERRY STRAWBERRY', 'BLUEBERRY' ]
The initial array has two text values. After applying the
transformation we get an array containing two text values
with uppercase letters.
console.log(arr);
//[ [ 'raspberry', 'strawberry' ], [ 'blueberry' ] ]
console.log(arr);
//[ 'raspberry', 'strawberry', 'blueberry' ]
console.log(arr);
//[ 'raspberry', 'strawberry', 'blueberry' ]
console.log(arr.flat());
//[ 1, 2, 3 ]
function map(f){
const newValue = f(value);
return M(newValue);
}
function flatMap(f){
const newValue = f(value);
return newValue;
}
return {
map,
flatMap
}
}
function f(){
return M(2)
}
M(1)
.flatMap(f);
//M(2)
f(1);
//M(2) Right Identity Passing the M function into the flatMap
operation of a monad wrapping a value should result in monad
wrapping the same value.
M(1)
.flatMap(M)
//M(1) Associativity Monads must respect the associativity
law, meaning that: monad.flatMap(f).flatMap(g)
=== monad.flatMap(x => f(x).flatMap(g)) Let’s test the law.
function f(){
return M(2)
}
function g(){
return M(3)
}
M(1)
.flatMap(f)
.flatMap(g);
//M(3)
M(1)
.flatMap(n => f(n).flatMap(g));
//M(3) Alternative Creation and Naming The flat mapping method
comes with different names like bind , chain , or just flatMap
. There is also the practice to create the monad with a
different method called unit . unit does the same thing as the
M factory function does, it creates a new monad.
function bind(f){
const newValue = f(value);
return newValue;
}
return {
map,
bind
}
}
function unit(value){
return M(value);
}
return {
unit
}
})(); unit creates a new monad wrapping a value.
function f(){
return M.unit(2)
}
M.unit(1).bind(f)
//M 2
Below you can review how the monad rules can be written
in this case.
Associativity M.unit(1).bind(f).bind(g)
//M 3
monad.bind(f).bind(g)
=== monad.bind(x => f(x).bind(g))
monad.flatMap(f).flatMap(g)
=== monad.flatMap(x => f(x).flatMap(g))) This being said, I
consider that the first implementation of the identity monad as
an object with map and flatMap method is easier to work with.
function split(text){
return text.split(" ");
}
Array.of('mango')
.flatMap(duplicate)
//['mango', 'mango']
duplicate('mango');
//['mango', 'mango']
Array.of('lemon')
.flatMap(Array.of)
//['lemon']
Associativity //monad.flatMap(f).flatMap(g)
// === monad.flatMap(x => f(x).flatMap(g)))
Array.of('orange kiwi')
.flatMap(split)
.flatMap(duplicate);
//[ 'orange', 'orange', 'kiwi', 'kiwi' ]
Array.of('orange kiwi')
.flatMap(s => split(s).flatMap(duplicate))
//[ 'orange', 'orange', 'kiwi', 'kiwi' ]
console.log(newList)
// List []
function inCountry(country){
return function(destination){
return destination.country === country;
}
}
list
.filter(inCountry('Portugal'))
//List [
// {name:'Cascais',country:'Portugal'},
// {name:'Sintra',country:'Portugal'}]
console.log(countryMap.get("3"));
//{ name: 'UK' }
has(key) returns true if the given key exists within the map,
otherwise, it returns false .
delete(key) returns a new Map with the specified key
removed.
const newMap = countryMap.delete("3");
console.log(newMap.has("3"));
//false
console.log(newMap.get("3"));
//undefined clear() returns a new Map containing no key-values
pairs.
Lists have the filter , map , reduce , and sort methods working
similar to the ones available in arrays.
function double(n){
return n * 2;
}
function filter(test) {
return function reducer(arr, value) {
if (test(value)) {
return [...arr, value]
} else {
return arr;
}
}
}
function isEven(n){
return n % 2 === 0;
}
Note that reduce does not stop and processes all the values
in the array. The find method stops when it finds the value.
The equivalent implementation of find using the reduce
method performs worse because it loops through all the
elements.
function find(test) {
return function reducer(foundValue, value) {
if (test(value) && !foundValue) {
return value
} else {
return foundValue;
}
}
}
function isEven(n){
return n % 2 === 0;
}
function double(n){
return n * 2;
}
function isEven(n){
return n % 2 === 0;
}
function double(n){
return n * 2;
}
function logIdentity(n){
console.log(n);
return n;
}
Range(1, 4)
.forEach(logIdentity);
//1
//2
//3
function isPrime(number){
if (number <= 1){
return false;
}
return true;
}
Range(0, 10)
.filter(isPrime)
.forEach(log);
function isPrime(number){ }
function log(number) {
console.log(number);
}
Range(0, 10)
.filter(isPrime)
.forEach(log);
function isPrime(number) {
if (number > 2){
const firstDivisor = findFirstDivisor(number);
return !firstDivisor;
} else {
return (number === 2)
? true
: false;
}
}
function findFirstDivisor(number){
return Range(2, (number / 2) + 1)
.find(isDivisorOf(number));
}
function isDivisorOf(number) {
return function(divisor) {
return (number > 1)
? number % divisor === 0
: false
};
}
function log(number) { }
function toArray(generate) {
let arr = [];
let value = generate();
Here is how you can use take and forEach in a pipeline to get
and log to console the first three values from an infinite
sequence of numbers.
import { pipe } from 'lodash/fp';
delay(1000)
.then(logDone); delay creates a promise that will resolve in a
specific amount of time. We can use it to delay the execution
of a task.
function logIdentity(value){
console.log(value);
return value;
}
Promise.resolve('sTudY')
.then(logIdentity) //"sTudY"
.then(toUpperCase)
.then(logIdentity); //"STUDY"
function logIdentity(value){}
Promise.resolve(9)
.then(logIdentity) //9
.then(n => devideB(n, 3))
.then(logIdentity); //3
Promise.resolve(9)
.then(logIdentity) //9
.then(devideBy(3))
.then(logIdentity); //3
Promise.resolve(9)
.then(logIdentity) //9
.then(devideBy(3))
.then(logIdentity); //3
function logError(error){
console.error(error);
}
Promise.resolve(9)
.then(logIdentity) //9
.then(devideBy(0))
.then(logIdentity)
.catch(logError) //"Can't divide by 0"
function toChangeAction(temperature){
return {
type : 'CHANGE_TEMPERATURE',
temperature
}
}
function logIdentity(value){}
Promise.resolve(280)
.then(logIdentity)
.then(toCelsius)
.then(logIdentity)
.then(toChangeAction)
.then(logIdentity);
//280
//6.85
//{ type: 'CHANGE_TEMPERATURE', temperature: 6.85 }
toCelsius takes the 280 -kelvin temperature as input and
returns 6.85 . toChangeAction takes as input the 6.85 -celsius
temperature and returns an action object containing the
new celsius value.
{
"scripts": {
"dev": "parcel index.html"
}
}
Now, you can start the development server and access the
index.html page.
Fetch
function logIdentity(value){}
function logError(error){
console.error(error);
}
fetch('https://api.github.com/gists/public')
.then(toJson)
.then(logIdentity)
.catch(logError); The json() method converts the response to a
JavaScript object. It returns a promise wrapping the result of
parsing the response body text as JSON .
const todosPromise =
fetch('/todos').then(toJson);
Promise.race([
dictionariesPromise,
todosPromise
]).then(logIdentity); Promise.all Promise.all takes an array of
promises and returns a new promise that resolves when all of
the input’s promises have resolved. If one of the input
promises it rejected it rejects immediately.
Promise.allSettled([
dictionariesPromise,
todosPromise,
rejectedPromise
])
.then(logIdentity)
//[
//{status: "fulfilled", value: Array(1)},
//{status: "fulfilled", value: Array(1)},
//{status: "rejected", reason: "Error"}
//]
Promise.resolve(1)
.then(identity)
//Promise 1
The composition law is respected when mapping functions
don’t return promises. In this case, promise.then(f).then(g)
gives the same result as promise.then(x => g(f(x))) .
function f(x){
return x + 1;
}
function g(x){
return x * 2;
}
function logIdentity(value){}
Promise.resolve(1)
.then(f)
.then(g)
.then(logIdentity)
Promise.resolve(1)
.then(x => g(f(x)))
.then(logIdentity) The functor composition law is broken when
one transformation function returns a promise and the other
doesn’t. Here is an example.
function f(x){
return Promise.resolve(x + 1);
}
function g(x){
return x * 2;
}
Promise.resolve(1)
.then(f)
.then(g)
.then(logIdentity);
//4
Promise.resolve(1)
.then(x => g(f(x)))
.then(logIdentity);
//NaN
promise
.then(x => g(f(x)) Monad Laws The then method on a promise
acts like flatMap when the transformation function returns a
promise.
Promise.resolve(1)
.then(f)
//Promise 2
f(1)
//Promise 2
Promise.resolve(1)
.then(unit)
//Promise 1
function logIdentity(value){}
Promise.resolve(1)
.then(f)
.then(g)
.then(logIdentity);
Promise.resolve(1)
.then(x => f(x).then(g))
.then(logIdentity); Summing-up, when both mapping functions
return a value, that is not a promise, than acts as map and
composition is respected. When both mapping functions return a
promise, than acts as flatMap and associativity is respected.
ajax('https://api.github.com/gists/public')
.subscribe(console.log); The observable stream created with
the ajax function emits the response object returned from the
request.
dataSource
.pipe(
map(double)
)
.subscribe(console.log);
//2
//4
//6
function isEven(n){
return n % 2 === 0;
}
dataSource
.pipe(
filter(isEven)
)
.subscribe(console.log);
//2
//4
The take operator allows selecting only the first n emitted
values from an observable source. Below is an example of
selecting the first three values from the source.
import { of } from 'rxjs';
import { take } from 'rxjs/operators';
function isEven(n){
return n % 2 === 0;
}
function double(n){
return n * 2;
}
dataSource
.pipe(
filter(isEven),
map(double)
)
.subscribe(console.log);
//4
//8
observable.subscribe(observer);
//Next: 1
//Next: 2
//Error: Failed There is no need to have a callback in the
observer object for all notification types. Some notifications
can be ignored.
observable.subscribe(
value => console.log(`Next: ${value}`),
msg => console.error(`Error: ${msg}`),
() => console.error(`Complete`)
); Subscriptions A subscription is an object created when
subscribing to an observable. It allows us to unsubscribe form
that observable.
//later
setTimeout(() => {
subscription.unsubscribe();
}, 3500);
//0 -> after 1s
//1 -> after 2s
//2 -> after 3s The subscription object has just the
unsubscribe method. When all subscribers unsubscribe from an
observable, that observable stops its execution.
observable.pipe(
map(ajax.getJSON)
).subscribe(console.log);
//Observable {}
//Observable {}
observable.pipe(
map(ajax.getJSON),
concatAll()
).subscribe(console.log);
//[{name: 'Dictionary'}]
//[{title: 'To Do'}]
function createGetNTerm(c){
return function(n){
return n * c + 1
}
}
toSequence(4)
//Observable 5 9 13
from([2, 4]).pipe(
map(toSequence)
)
.subscribe(console.log);
//Observable 3 5 7
//Observable 5 9 13
forkJoin({
dictionaries: dictionariesObservable,
todos: todosObservable
}).subscribe(console.log);
//{dictionaries: Array(), todos: Array()}
const dictionariesObservable =
ajax.getJSON('/dictionaries');
const todosObservable =
ajax.getJSON('/todos');
function identity(n){
return n;
}
of(1, 2, 3)
.pipe(
map(identity)
)
//Observable 1,2,3
function increment(n){
return n + 1;
}
function double(n){
return n * 2
}
of(1, 2, 3)
.pipe(
map(increment),
map(double)
)
//Observable 4,6,8
of(1, 2, 3)
.pipe(
map(x => double(increment(x)))
)
//Observable 4,6,8
function f(n){
return of(n + 1);
}
of(1)
.pipe(
mergeMap(f)
)
//Observable 2
f(1)
//Observable 2
of(1)
.pipe(
mergeMap(unit)
)
.subscribe(console.log);
//Observable 1
Associativity monad.flatMap(f).flatMap(g)
=== monad.flatMap(x => f(x).flatMap(g))) Here is how we can
translate the law for observables.
function g(n){
return of(n * 2);
}
of(1)
.pipe(
mergeMap(f),
mergeMap(g)
)
//Observable 4
of(1)
.pipe(
mergeMap(x => f(x).pipe(mergeMap(g)))
)
//Observable 4
initialModel : Model
initialModel = { number = 0 }
Decrement ->
{ model | number = model.number - 1 }
The initial number is zero and it increases and decreases
when the user presses different buttons.
//Code
main =
Browser.sandbox {
init = initialModel,
update = update,
view = view
}
The view function takes the initial model and displays on the
screen. The user actions generate messages. The update
handles those action and updates the model. When the
model is changed the view function is called to render the
new model on screen. This flow repeats endlessly.
function Decrement(){
return {
type : Decrement.name
}
}
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<Counter />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
Below is how the data flow looks like when using commands
for handling impure logic.
Random Number with React and Redux Now
let’s implement the same functionality in
JavaScript.
function LoadTodos(){
return {
type: LoadTodos.name,
payload: {
request:{
url:'/todos'
}
}
}
}
The List view function is pure. It takes the list of todos and
creates an HTML. It creates a refresh button that sends
LoadTodos messages on clicks.
All these data structures wrap one or more values and allow
us the create pipelines of pure functions transforming the
wrapped data. Usually, there are a set of common
transformations like filter , map , reduce , or sort that all
these data structures support.