* 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