Gleb Bahmutov PRO
JavaScript ninja, image processing expert, software quality fanatic
* Functional
source: Improved Minesweeper
Node, C++, C#,
Angular, Vue.js, React, server-side etc.
event analysis and statistics for financial markets
Boston and New York
number of engineers
lifetime of the company
const todos = getTodos()
const updatedTodos = addNewTodo(todos, newTodo)
saveTodos(updatedTodos)
where does it get the data?
how does it save the data?
is this object modified?
const todos = getTodos()
const updatedTodos = addNewTodo(todos, newTodo)
saveTodos(updatedTodos)
what if getTodos() is called again
while the first save is still executing?
const todos = getTodos()
const updatedTodos = addNewTodo(todos, newTodo)
saveTodos(updatedTodos)
functional: A way to write your program in terms of simple functions
Everyone has their own path to JS
var numbers = [3, 2, 8]
var constant = 2
// 4 16
Problem: given an array of numbers, multiply even values by a constant and print the result
Write a solution using a for loop
Start using https://tonicdev.com/bahmutov/fun
var numbers = [3, 2, 8]
var constant = 2
var k = 0
for (k = 0; k < numbers.length; k += 1) {
if (numbers[k] % 2 === 0) {
console.log(numbers[k] * constant)
}
}
We command the computer to iterate through the list of numbers
var numbers = [3, 2, 8]
var constant = 2
var k = 0
for (k = 0; k < numbers.length; k += 1) {
if (numbers[k] % 2 === 0) {
console.log(numbers[k] * constant)
}
}
var numbers = [3, 2, 8]
var constant = 2
var k = 0
for (k = 0; k < numbers.length; k += 1) {
if (numbers[k] % 2 === 0) {
console.log(numbers[k] * constant)
}
}
for loop at some point will betray you
var numbers = [3, 2, 8]
var constant = 2
var k = 0
for (k = 0; k < numbers.length; k += 1) {
if (numbers[k] % 2 === 0) {
console.log(numbers[k] * constant)
}
}
var k = 0
for (k = 0; k < numbers.length; k += 1) {
if (numbers[k] % 2 === 0) {
console.log(numbers[k] * constant)
}
}
Problem: extract functions that can be described with a single sentence
function mul(a, b) {
return a * b
}
function print(x) {
console.log(x)
}
function isEven(x) {
return x % 2 === 0
}
for (k = 0; k < numbers.length; k += 1) {
if (isEven(numbers[k])) {
print(mul(numbers[k], constant))
}
}
multiplies two numbers
prints one argument
returns true if x is even
var k = 0
for (k = 0; k < numbers.length; k += 1) {
if (numbers[k] % 2 === 0) {
console.log(numbers[k] * constant)
}
}
function mul(a, b) {
return a * b
}
function print(x) {
console.log(x)
}
function isEven(x) {
return x % 2 === 0
}
for (k = 0; k < numbers.length; k += 1) {
if (isEven(numbers[k])) {
print(mul(numbers[k], constant))
}
}
Hmm, did we just blow up the size of the code for no reason?
initial spaghetti code
mul
isEven
...
short readable code
"atoms"
compare: multiply even numbers by a constant
if (numbers[k] % 2 === 0) {
console.log(numbers[k] * constant)
}
if (isEven(numbers[k])) {
print(mul(numbers[k], constant))
}
almost reads like the problem itself
function mul(a, b) {
return a * b
}
function print(x) {
console.log(x)
}
function isEven(x) {
return x % 2 === 0
}
Problem: which function is NOT like the other two?
hint: look where the variables are coming from
function mul(a, b) {
return a * b
}
function print(x) {
console.log(x)
}
function isEven(x) {
return x % 2 === 0
}
Problem: which function is NOT like the other two?
function mul(a, b) {
return a * b
}
only uses its arguments
same arguments => same result
does not modify the outside environment
function print(x) {
console.log(x)
}
uses outside variable
modifies the outside environment
function mulBy(K) {
return function (x) {
return K * x
}
}
function mulBy(K) {
return function (x) {
return K * x
}
}
function mulBy(K) {
return function (x) {
return K * x
}
}
pure
impure
function mulBy(K) {
return function (x) {
return K * x
}
}
const double = mulBy(2)
console.log(double.toString())
// function (x) {
// return K * x
// }
function mulBy(K) {
return function (x) {
return K * x
}
}
const double = mulBy(2)
/*
double is same as
K = 2
function (x) {
return K * x
}
*/
function mul(a, b) {
return a * b
}
const constant = 2
for (...) {
mul(constant, numbers[k]))
}
function and one argument are known early
function mul(a, b) {
return a * b
}
function mulBy = /* something here */;
var constant = 2
var byConstant = mulBy(constant)
for (...) {
byConstant(numbers[k])
}
function mul(a, b) {
return a * b
}
function mulBy(K) {
return function(x) {
return mul(K, x)
}
}
const constant = 2
const byConstant = mulBy(constant)
for (...) {
byConstant(numbers[k])
}
function mul(a, b) {
return a * b
}
const mulBy = K => x => mul(K, x)
const constant = 2
const byConstant = mulBy(constant)
for (...) {
byConstant(numbers[k])
}
function mul(a, b) {
return a * b
}
const mulBy = K => x => mul(K, x)
const constant = 2
const byConstant = mulBy(constant)
for (...) {
byConstant(numbers[k])
}
function mul(a, b) {
return a * b
}
const mulBy = K => x => K * x
const constant = 2
const byConstant = mulBy(constant)
for (...) {
byConstant(numbers[k])
}
const mulBy = K => x => K * x
const double = mulBy(2)
console.log(double.toString())
// x => K * x
hint: "eval" uses current lexical scope
// >>> insert statement here <<<
const triple
= eval('(' + double.toString() + ')')
console.log(triple(10))
// 30
K = 3
// eval('(' + double.toString() + ')')
const triple = eval('(x => K * x)')
console.log(triple(10))
// 30
function mul(a, b) {
return a * b
}
const constant = 2
const byConstant = /* something here */
hint: look at Function.prototype.bind
function mul(a, b) {
return a * b
}
const constant = 2
const byConstant = mul.bind(null, constant)
// byConstant(10) = 20
// byConstant(-1) = -2
function mul(a, b) {
return a * b
}
const constant = 2
const byConstant = mul.bind(null, constant)
function savePassword(userId, newPassword)
// vs
function savePassword(newPassword, userId)
function savePassword(userId, newPassword) {...}
function onLogin(userId) {
return {
savePassword: savePassword.bind(null, userId),
deleteAccount: ...
}
}
function mul(a, b) {
return a * b
}
function print(x) {
console.log(x)
}
function isEven(x) {
return x % 2 === 0
}
const byConstant = mul.bind(null, constant)
var k
for (k = 0; k < numbers.length; k += 1) {
if (isEven(numbers[k])) {
print(byConstant(numbers[k]))
}
}
fn
fn
data
for (k = 0; k < numbers.length; k += 1) {
if (isEven(numbers[k])) {
print(byConstant(numbers[k]))
}
}
fn
fn
data
mulAndPrint = compose(print, byConstant)
mulAndPrint(numbers[k])
mulAndPrint = compose(print, byConstant)
mulAndPrint(numbers[k])
mulAndPrint = compose(print, byConstant)
mulAndPrint(numbers[k])
function compose()
mulAndPrint = compose(print, byConstant)
mulAndPrint(numbers[k])
function compose(f, g)
mulAndPrint = compose(print, byConstant)
mulAndPrint(numbers[k])
function compose(f, g) {
return function() {
}
}
mulAndPrint = compose(print, byConstant)
mulAndPrint(numbers[k])
function compose(f, g) {
return function(x) {
}
}
mulAndPrint = compose(print, byConstant)
mulAndPrint(numbers[k])
function compose(f, g) {
return function(x) {
return f(g(x))
}
}
mulAndPrint = compose(print, byConstant)
for (k = 0; k < numbers.length; k += 1) {
if (isEven(numbers[k])) {
mulAndPrint(numbers[k])
}
}
fn
data
function compose(f, g) {
return function(x) {
return f(g(x))
}
}
const F = compose(f, g)
F(x)
mulAndPrint = compose(print, byConstant)
print "pollutes" the composed function
initial spaghetti code
mul
isEven
...
short readable code
.bind, compose
const byConstant = mul.bind(null, constant)
const mulAndPrint = compose(print, byConstant)
var k = 0
for (k = 0; k < numbers.length; k += 1) {
if (isEven(numbers[k])) {
mulAndPrint(numbers[k])
}
}
var k
for (k = 0; k < list.length; k += 1) {
// call fn(list[k])
}
var k = 0
for (k = 0; k < numbers.length; k += 1) {
if (isEven(numbers[k])) {
print(byConstant(numbers[k]))
}
}
filter
map
side effect
function filter(fn, list) {
var k = 0
const result = []
for (k = 0; k < list.length; k += 1) {
if (fn(list[k])) {
result.push(list[k])
}
}
return result
}
// filter(isEven, [1, 2, 3])
// [2]
callback first
filter(isEven, [1, 2, 3])
evenNumbers = filter.bind(null, isEven)
evenNumbers([1, 2, 3])
// [2]
evenNumbers = filter.bind(null, isEven)
evenNumbers([1, 2, 3])
// [2]
function map(fn, list) {
var k = 0
const result = []
for (k = 0; k < list.length; k += 1) {
result.push(fn(list[k]))
}
return result
}
// map(byConstant, [1, 2, 3])
// [2, 4, 6]
map(byConstant, numbers)
multiplyAll = map.bind(null, byConstant)
multiplyAll([1, 2, 3])
// [2, 4, 6]
function forEach(fn, list) {
var k = 0
for (k = 0; k < list.length; k += 1) {
fn(list[k])
}
}
// forEach(print, [1, 2, 3])
// 1 2 3
forEach(print, map(byConstant, filter(isEven, numbers)))
const onlyEven = filter.bind(null, isEven)
forEach(print, map(byConstant, onlyEven(numbers)))
const onlyEven = filter.bind(null, isEven)
const multiply = map.bind(null, byConstant)
forEach(print, multiply(onlyEven(numbers)))
const onlyEven = filter.bind(null, isEven)
const multiply = map.bind(null, byConstant)
const printAll = forEach.bind(null, print)
printAll(multiply(onlyEven(numbers)))
const onlyEven = filter.bind(null, isEven)
const multiply = map.bind(null, byConstant)
const printAll = forEach.bind(null, print)
printAll(multiply(onlyEven(numbers)))
const onlyEven = filter.bind(null, isEven)
const multiply = map.bind(null, byConstant)
const printAll = forEach.bind(null, print)
const compose = (f, g, h) => x => f(g(h(x)))
const solution = compose(printAll, multiply, onlyEven)
solution(numbers)
fn
data
const onlyEven = filter.bind(null, isEven)
const multiply = map.bind(null, byConstant)
const printAll = forEach.bind(null, print)
const compose = (f, g, h) => x => f(g(h(x)))
const solution = compose(printAll, multiply, onlyEven)
solution(numbers)
ugly!
// filter is a binary function
const onlyEven = filter(isEven)
onlyEven([1, 2, 3])
// [2]
function map(fn) {
return function(list) {
var k = 0
const result = []
for (k = 0; k < list.length; k += 1) {
result.push(fn(list[k]))
}
return result
}
}
const multiply = map(byConstant)
multiply([1, 2, 3])
const onlyEven = filter(isEven)
const multiply = map(byConstant)
const printAll = forEach(print)
const compose = (f, g, h) => x => f(g(h(x)))
const solution = compose(printAll, multiply, onlyEven)
solution(numbers)
const onlyEven = filter(isEven)
const multiply = map(byConstant)
const printAll = forEach(print)
const compose = (f, g, h) => x => f(g(h(x)))
const solution = compose(printAll, multiply, onlyEven)
solution(numbers)
multiply even numbers by a constant and print the result
const onlyEven = filter(isEven)
const multiply = map(byConstant)
const printAll = forEach(print)
const pipe = (f, g, h) => x => h(g(f(x)))
const solution = pipe(onlyEven, multiply, printAll)
solution(numbers)
function map(fn, list) { ... }
curry(map)(byConstant)([1, 2, 3])
// [2, 4, 6]
function curry(fn) {
return function (x) {
return function (y) {
return fn(x, y)
}
}
}
const curry = fn => x => y => fn(x, y)
const map = curry((fn, list) => {
var k = 0
const result = []
for (k = 0; k < list.length; k += 1){
result.push(fn(list[k]))
}
return result
})
const solution = pipe(
filter(isEven),
map(byConstant),
forEach(print)
)
solution(numbers)
const solution = pipe(
filter(x => isEven(x)),
map(y => byConstant(y)),
forEach(z => print(z))
)
solution(numbers)
Anonymous arrow functions are redundant
const solution = pipe(
filter(isEven),
map(byConstant),
forEach(print)
)
solution(numbers)
const solution = pipe(
filter(isEven),
map(byConstant),
forEach(print)
)
solution(numbers)
// filter :: fn => [x] => [x]
// [x] => [x]
// [x] => [x]
// [x] => undefined
// solution :: [x] => undefined
// numbers is [x]
// undefined
console.log(['1','2','3'].map(parseFloat))
// 1, 2, 3
console.log(['1','2','3'].map(parseInt))
// 1, NaN, NaN
// parseFloat :: string => number
console.log(['1','2','3'].map(parseFloat))
// parseInt :: string, radix => number
console.log(['1','2','3'].map(parseInt))
// 1, NaN, NaN
// Array.prototype.map :: cb
// where cb :: item, index, array
// parseInt :: string, radix => number
console.log(['1','2','3'].map(parseInt))
parseInt('1', 0) // 1
parseInt('2', 1) // NaN
parseInt('3', 2) // NaN
// function parseInt(x, radix)
// but Array.map is (value, index, array)
['1', '2', '3'].map(function (x) {
return parseInt(x)
})
// parseInt :: string, radix => number
unary(parseInt)('2', 1) // 2
['1','2','3'].map(unary(parseInt))
// [1, 2, 3]
// parseInt :: string, radix => number
const unary = f => x => f(x)
// unary(parseInt) :: string => number
['1','2','3'].map(unary(parseInt))
// [1, 2, 3]
// parseInt :: string, radix => number
const parse10 = partialRight(parseInt, 10)
['1','2','3'].map(parse10)
// [1, 2, 3]
const partialRight = (f, b) => a => f(a, b)
// parseInt :: string, radix => number
const parse10 = partialRight(parseInt, 10)
// parse10 :: string => number
['1', '2', '3'].map(parse10)
// 1, 2, 3
function fn(a, b, c) { ... }
var newFn = fn.bind(null, valueA, valueB);
// or
var _ = require('lodash');
var newFn = _.partial(fn, valueA, valueB);
// or
var R = require('ramda');
var newFn = R.partial(fn, valueA, valueB);
const base10 = _.partialRight(parseInt, 10)
['1', '2', '3'].map(base10);
// [1, 2, 3]
// radix is bound,
// index and array arguments are ignored
var S = require('spots');
const base10 = S(parseInt, S, 10)
['1', '2', '3'].map(base10);
// [1, 2, 3]
// divide by 10
function divide(a, b) { return a / b; }
var selective = require('heroin');
var by10 = selective(divide, { b: 10 });
console.log(by10(10)); // 1 (a = 10, b = 10)
console.log(by10(2)); // 0.2 (a = 2, b = 10)
function fn(options) { ... }
var obind = require('obind');
var withBar = obind(fn, { bar: 'bar' });
withBar({ baz: 'baz' });
/*
equivalent to
foo({
bar: 'bar',
baz: 'baz'
})
*/
var numbers = [3, 2, 8]
var constant = 2
// 4
// 16
const R = require('ramda')
var numbers = [3, 2, 8]
var constant = 2
const solution = R.pipe(
R.filter(isEven),
R.map(R.multiply(constant)),
R.forEach(print)
)
solution(numbers)
const R = require('ramda')
var numbers = [3, 2, 8]
var constant = 2
const solution = R.pipe(
R.filter(isEven),
R.map(R.multiply(constant)),
R.forEach(print)
)
solution(numbers)
const R = require('ramda')
var numbers = [3, 2, 8]
var constant = 2
const solution = R.pipe(
R.filter(isEven),
R.map(R.multiply(constant)),
R.forEach(print)
)
solution(numbers)
6 functions
2 values
forEach(console.log, [1, 2, 3])
Error: Illegal invocation
forEach(console.log.bind(console), [1, 2, 3])
const numbers = () => Promise.resolve([3, 2, 8])
hint: look at R.pipeP
const numbers = () => Promise.resolve([3, 2, 8])
const solution = R.pipeP(
numbers,
R.filter(isEven),
R.map(R.multiply(constant)),
R.forEach(print)
)
solution()
// 4
// 16
const Numbers = (numbers) => (
<ul>
{R.map(x => (<li>{x}</li>), numbers)}
</ul>
)
const Container = children => (
<div className="container">
<h1>Numbers are</h1>
{children}
</div>
)
const App = appState => (
Container(Numbers(appState.numbers))
)
list iteration
composition
const App = appState => (
Container(Numbers(appState.numbers))
)
// same as
const App = R.compose(
Container, Numbers, R.prop('numbers')
)
list of books, tutorials, libraries and people
By Gleb Bahmutov
The principles and basics of functional programming in JavaScript using small coding problems. Pure functions, partial application, composition, iteration, lexical scope, Ramda library.
JavaScript ninja, image processing expert, software quality fanatic