Introduction to
Functional Programming
Agenda
* Show some functional programming and try to sell it to you.
Disclaimer
* May clash with some of the Google’s style guide and best practices because of the nature of the languages that we use.
* The syntax might not be accurate at some places.
* The aim of this talk is not to hurt any language’s or programmer’s emotions.
Immutability
a = 5;
b = func(a);
print(a);
// What should a be?
Immutability
a = 5;
b = func(a);
print(a);
// What should a be?
a = [1, 2, 3];
b = func(a);
print(a);
// What should a be?
Immutability
a = 5;
b = func(a);
print(a);
// What should a be?
a = [1, 2, 3];
b = func(a);
print(a);
// What should a be?
func(a) -> func2(a) -> func3(a) -> . . . -> func545145564(a) { a[1] = 10; }
Immutability
a = 5;
b = func(a);
print(a);
// What should a be?
a = [1, 2, 3];
b = func(a);
print(a);
// What should a be?
func(a) -> func2(a) -> func3(a) -> . . . -> func545145564(a) { a[1] = 10; }
Immutability
* Mutability leads to inconsistent APIs.
Immutability
* Mutability leads to inconsistent APIs.
a = “abc”;
a2 = a.capitalize();
// What can a and a2 be?
Immutability
* Mutability leads to inconsistent APIs.
a = “abc”;
a2 = a.capitalize();
// What can a and a2 be?
a | a2 |
“abc” | “ABC” |
“ABC” | null/undefined etc. |
“ABC” | “ABC” |
Immutability
* Mutability leads to inconsistent APIs.
a = “abc”;
a2 = a.capitalize();
// What can a and a2 be?
a | a2 |
“abc” | “ABC” |
“ABC” | null/undefined etc. |
“ABC” | “ABC” |
Immutability
* All values are immutable.
* A value is a value: primitive or otherwise.
* You cannot edit a value passed as reference.
* Immutable data-structures, also called as persistent data structures.
* a.push(4); is bad as it changes a for all the references.
* Decouples time from computation.
* Makes programs easy to read and reason about.
a = [1, 2, 3];
b = func(a);
print(a);
// What should a be?
What. Not how. (Higher order functions)
in = [1, 2, 3];
out = []; // How
for (i = 0; i < in.length; i++) { // How
out[i] = in[i] * 2; // What
}
What. Not how. (Higher order functions)
in = [1, 2, 3];
out = []; // How
for (i = 0; i < in.length; i++) { // How
out[i] = in[i] * 2; // What
}
out2 = []; // How
for (i = 0; i < in.length; i++) { // How
out2[i] = in[i] * 3; // What
}
What. Not how. (Higher order functions)
in = [1, 2, 3];
out = []; // How
for (i = 0; i < in.length; i++) { // How
out[i] = in[i] * 2; // What
}
// map :: ((a -> b), [a]) -> [b]
// Encapsulates the how in one place
def map(func, arr) {
if (arr.length === 0) {
return [];
} else {
return [func(head(arr))] ++ map(func, tail(arr));
}
}
// No for loops! Less moving parts. Less places to make mistakes.
out2 = []; // How
for (i = 0; i < in.length; i++) { // How
out2[i] = in[i] * 3; // What
}
What. Not how. (Higher order functions)
in = [1, 2, 3];
out = []; // How
for (i = 0; i < in.length; i++) { // How
out[i] = in[i] * 2; // What
}
// map :: ((a -> b), [a]) -> [b]
// Encapsulates the how in one place
def map(func, arr) {
if (arr.length === 0) {
return [];
} else {
return [func(head(arr))] ++ map(func, tail(arr));
}
}
// No for loops! Less moving parts. Less places to make mistakes.
out2 = []; // How
for (i = 0; i < in.length; i++) { // How
out2[i] = in[i] * 3; // What
}
out = map(x => x * 2, in); // What
out2 = map(x => x * 3, in); // What
What. Not how. (Higher order functions)
// filter :: ((a -> Bool), [a]) -> [a]
def filter(predFunc, arr) {
if (arr.length === 0) {
return [];
} else {
h = head(arr);
return (predFunc(h) ? [h] : []) ++ filter(predFunc, tail(arr));
}
}
// No for loops!
What. Not how. (Higher order functions)
// reduce :: ((a, b) -> b, b, [a]) -> b
def reduce(accFunc, acc, arr) {
if (arr.length === 0) {
return acc;
} else {
h = head(arr);
return reduce(accFunc, accFunc(acc, h), tail(arr));
}
}
// No for loops!
What. Not how. (Higher order functions)
// reduce :: ((a, b) -> b, b, [a]) -> b
def reduce(accFunc, acc, arr) {
if (arr.length === 0) {
return acc;
} else {
h = head(arr);
return reduce(accFunc, accFunc(acc, head), tail(arr));
}
}
// No for loops!
reduce(add2, 0, [1, 2, 3])
-> reduce(add2, add2(0, 1), [2, 3])
-> reduce(add2, 1, [2, 3])
-> reduce(add2, add2(1, 2), [3])
-> reduce(add2, 3, [3])
-> reduce(add2, add2(3, 3), [])
-> reduce(add2, 6, [])
-> 6
(((0 + 1) + 2) + 3)
Todo
* Try to implement map, filter and reduce in your language.
* Try to implement map and filter using reduce.
Example
* Multiply all even numbers in an array by 10 and add them.
Example
const numList = [1, 2, .., 10];
let result = 0;
for (let i = 0; i < numList.length; i++) {
if (numList[i] % 2 === 0) {
result += numList[i] * 10;
}
}
* Multiply all even numbers in an array by 10 and add them.
Example
const numList = [1, 2, .., 10];
let result = 0;
for (let i = 0; i < numList.length; i++) {
if (numList[i] % 2 === 0) {
result += numList[i] * 10;
}
}
const result =
[1, 2,.., 10]
.filter(n => n % 2 === 0) // filter even numbers: [2, 4, 6, 8, 10]
.map(a => a * 10) // multiply each by 10: [20, 40, 60, 80, 100]
.reduce((a, b) => a + b, 0); // add them: 300
* Multiply all even numbers in an array by 10 and add them.
Example
const numList = [1, 2, .., 10];
let result = 0;
for (let i = 0; i < numList.length; i++) {
if (numList[i] % 2 === 0) {
result += numList[i] * 10;
}
}
// all the hows are mixed together
// all the whats are mixed together
// all the hows and whats are mixed together
const result =
[1, 2,.., 10]
.filter(n => n % 2 === 0) // filter even numbers: [2, 4, 6, 8, 10]
.map(a => a * 10) // multiply each by 10: [20, 40, 60, 80, 100]
.reduce((a, b) => a + b, 0); // add them: 300
// clear separation of concerns
// easier to read, reason about and debug
// less cognitive load
// more extensible
// more malleable
* Multiply all even numbers in an array by 10 and add them.
What. Not how.
* Decouple what and how.
* You focus on what, not how.
* Increases reusability.
* Increases modularity.
* You think in terms of small do-one-thing functions.
Pointers (not the C ones)
* Use recursion. Avoid for loops.
* Referential transparency/pure functions: No reading apart from arguments, no writing to any other place. No side effects. Helps you to reason about your code easily.
* Try to avoid updating current state of data in your mind. Functions do not act on data. Functions take arguments and return results.
* Try to group your side effects towards the outer shell.
* A program is a transformation of data going through functions rather than state updates on an object.
* a.update(b) becomes a2 = update(a, b)
Recommendations
* Learn one functional programming language: Clojure, Elixir, Haskell
* Makes you a better programmer. Adds tools to your toolbelt. You know more patterns.
* It will seem very restrictive at first but once you get the hang of it, it’s a lot of fun!
Questions???