Background
- From Cincinnati, Ohio.
- University of Kentucky graduate.
- Developer at Losant, IOT
Contact
- Github: @rjhilgefort
- Twitter: @rjhilgefort
{.column}
Is
- A "101" talk
- Enablement to read/write FP in the real world
Is Not
- An intro to JS
- A deep dive into FP
- ADT Coverage
- Covering Hindly-Milner Notation
{.column}
Functional programming (FP) is a programming paradigm that is the process of building software by composing pure functions and avoiding shared state, mutable data, and side-effects.
{.column}
Other examples of programming paradigms include procedural programming and object oriented programming.
- Procedural programming is generally what you would call a "script", where instructions are a series of computational steps to be carried out.
- Object oriented programming (OOP), where application state is usually shared and colocated with methods in objects.
{.column}
- Declarative (vs Imperative)
- First Class Functions
- Higher Order Functions (HOF)
- Side Effects
- Purity
- Referential Transparency
- Immutability
- Composition
- Pointfree
- Currying
{.column}
- Predictability
- Testability
- Easy to reason about
- Easy to refactor
- Concurrency
- DRY
{.column}
Pure functions mean we always get the same output for a given input
Pure functions are easy to test because you don't have to "mock the world" for different cases
Declarative code indicates intent and desire, self documenting
FP encourages tiny composable methods (lego blocks) which are easy to reuse
Tiny composable methods are easy to move around and/or remove
Because FP apps are pure and the side effects delegated to the edges of the app, concurrency is much easier to achieve
Express the logic of a computation without describing its control flow.
Programming is done with expressions or declarations instead of statements. In contrast, imperative programming uses statements that change a program's state.
Imperative: I see that table located under the Gone Fishin’ sign is empty. My husband and I are going to walk over there and sit down.
Declarative: Table for two, please.
{.column}
// Imperative
// Double every number in list
const nums = [2, 5, 8];
for (let i = 0; i < nums.length; i++) {
nums[i] = nums[i] * 2
}
nums // [4, 10, 16]
{.column}
// Declarative
// Double every number in list
const double = x => x * 2
const nums = [2, 5, 8];
const numsDoubled = nums.map(double)
numsDoubled // [4, 10, 16]
First class function support means we can treat functions like any other data type and there is nothing particularly special about them - they may be stored in arrays, passed around as function parameters, assigned to variables, etc.
First class function support enables higher order functions- functions that work on other functions, meaning that they take one or more functions as an argument and can also return a function.
.
const getServerStuff = (callback) => {
return ajaxCall((json) => {
return callback(json)
})
}
// ... is the same as ...
const getServerStuff = ajaxCall;
{.column}
A pure function is a function that, given the same input, will always return the same output and does not have any observable side effect.
Basic rules:
- No outside scope
- Always returns a value
- Immutability
- Doesn't have any side effects
- referential transparency
- function composition
{.column}
// Impure
const names = [
'Michael', 'Erin', 'Dylan', 'Wes', 'Bao', 'Taron',
]
const maxNames = 5
const isValidNames = names => {
return names.length <= maxNames
}
isValidNames(names) // false
{.column}
// Pure
const names = [
'Michael', 'Erin', 'Dylan', 'Wes', 'Bao', 'Taron',
]
const isValidNames = names => {
const maxNames = 5
return names.length <= maxNames
}
isValidNames(names) // false
// Impure
const names = [
'Michael', 'Erin', 'Dylan', 'Wes', 'Bao', 'Taron',
]
let count
const getNamesCount = names => {
count = names.length
}
getNamesCount(names)
count // 6
{.column}
// Pure
const names = [
'Michael', 'Erin', 'Dylan', 'Wes', 'Bao', 'Taron',
]
const getNamesCount =
names => names.length
const count = getNamesCount(names)
count // 6
Immutability is a central concept of functional programming because without it, the data flow in your program is lossy. State history is abandoned, and strange bugs can creep into your software.
// Given the following
const foo = {
val: 2,
}
const addOneObjVal =
() => foo.val += 1
const doubleObjVal =
() => foo.val *= 2
{.column}
// The order of execution changes
// the output of the function
addOneObjVal() // { val: 3 }
doubleObjVal() // { val: 6 }
// `foo` is reset to original
doubleObjVal() // { val: 4 }
addOneObjVal() // { val: 5 }
// Given the following
const foo = {
val: 2,
}
const addOneObjVal =
x => Object.assign({}, x, { val: x.val + 1})
const doubleObjVal =
x => Object.assign({}, x, { val: x.val * 2})
{.column}
// Now it doesn't matter if we call the other
// method before any other method call that
// acts on `foo`.
addOneObjVal(foo) // { val: 3 }
addOneObjVal(foo) // { val: 3 }
addOneObjVal(foo) // { val: 3 }
doubleObjVal(foo) // { val: 4 }
doubleObjVal(foo) // { val: 4 }
// Combining as we did when mutating...
doubleObjVal(addOneObjVal(foo))
// { val: 6 }
A spot of code is referentially transparent when it can be substituted for its evaluated value without changing the behavior of the program.
const foo = (x, y) => {
if (x === y) return x
if (x < 5) return x * 2
return y
}
const bar = foo(2, 3)
bar // 4
// referential transparency says...
const bar = 4
bar // 4
{.column}
Because our method foo
is pure, we can use a technique called equational reasoning wherein one substitutes "equals for equals" to reason about code. It's a bit like manually evaluating the code without taking into account the quirks of programmatic evaluation.
Function composition is the process of combining two or more functions in order to produce a new function or perform some computation. For example, the composition f . g
(the dot means “composed with”) is equivalent to f(g(x))
in JavaScript.
// compose :: (Function -> Function) -> * a -> a
const compose = (f, g) => {
return x => {
return f(g(x))
}
}
const add1 = x => x + 1
const add2 = compose(add1, add1)
add2(2) // 4
{.column}
Our simple compose
function takes two functions, then a value x
, then calls those function from right-to-left with a value x
. By not providing the "data" right away, we're creating a new, reusable, function from our smaller function (Higher Order Functions).
Composition is associative (like addition), which means it doesn't matter how we group functions.
"Ramda" offers a ton of great FP tools, one of which is a compose
which handles any number of functions. Also offers a sister function in pipe
which processes from left-to-right. There are many other libraries which offer FP tools, including lodash/fp
.
import { compose } from 'ramda'
compose(console.log)('hi')
Pointfree style means never having to say your data. Functions never mention the data upon which they operate. First class functions, currying, and composition all play well together to create this style. Pointfree code can help us remove needless names and keep us concise and generic.
// Not pointfree
const toUpper = x => x.toUpper()
const exclaim = x => x.concat('!!!')
const loudExclaim = x => {
const upperX = toUpper(x)
return exclaim(upperX)
}
loudExclaim('losant') // LOSANT!!!
{.column}
// Pointfree
import {
toUpper, concat,
} from 'ramda'
const exclaim = concat('!!!')
const loudExclaim =
compose(exclaim, toUpper)
loudExclaim('losant') // LOSANT!!!
You can call a function with fewer arguments than it expects. It returns a function that takes the remaining arguments. Because of this, it is critical that your data
be supplied as the final argument (lodash
vs ramda
/lodash/fp
)
- Manual currying: Must be called one at a time.
- Auto-currying: Can be called like a normal function if desired.
// Manual currying
const add = (x) => {
return (y) => {
return (z) => {
return x + y + z
}
}
}
const add = x => y => z => {
return x + y + z
}
add(1, 2, 3) // Error
add(1)(2)(3) // 6
{.column}
// Auto-Currying
import { curry } from 'ramda'
const add = curry((x, y, z) => {
return x + y + z
})
add(1, 2, 3) // 6
add(1)(2)(3) // 6
import { __ } from 'ramda'
const add = x => y => x + y
const add5 = add(5)
add5(10) // 15
const subtract =
curry((x, y) => x - y)
const subtract5 =
subtract(__, 5)
subtract5(10) // 5
{.column}
import { replace } from 'ramda'
const noVowels = replace(/[aeiouy]/ig)
const censored = noVowels('*')
censored('Chocolate Rain')
// 'Ch*c*l*t* R**n'
.
That's it. Thanks.
Oh, this talk was stolen! I picked code examples and some definitions here and there from other articles and books that are free on the web. Here's a list of some resources to go check out if you liked this talk and/or want to learn more.
- Mostly Adequate Guide To Functional Programming
- Eric Elliott - What is Functional Programming?
- Anjana Vakil — Functional Programming in JS: What? Why? How?
- Awesome FP
{.column}
Feedback
You think I missed something? Saw a silly typo? Just generally want to improve upon this talk? You can help me fix it!
I made this talk using md2googleslides which allowed me to write the whole thing in markdown and build it to Google Slides. You can submit a PR to the repo so the next time I give the talk, it'll suck less!