Skip to content

Latest commit

 

History

History
1068 lines (563 loc) · 19.7 KB

File metadata and controls

1068 lines (563 loc) · 19.7 KB

Intro To Functional JavaScript

Rob Hilgefort

{.background}


whoami

Background

Contact

{.column}

.


What This Is, What This Is Not

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}

.


.{.background}


Functional Programming

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}

.


Functional Programming

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}

.


FP Concepts, Terms, Jargon

  • Declarative (vs Imperative)
  • First Class Functions
  • Higher Order Functions (HOF)
  • Side Effects
  • Purity
  • Referential Transparency
  • Immutability
  • Composition
  • Pointfree
  • Currying

{.column}

.


That's A Lot

Why should I bother?

{.background}


Why Functional Programming

  • Predictability
  • Testability
  • Easy to reason about
  • Easy to refactor
  • Concurrency
  • DRY

{.column}

.


Predictability

Pure functions mean we always get the same output for a given input


Testability

Pure functions are easy to test because you don't have to "mock the world" for different cases


Easy To Reason About

Declarative code indicates intent and desire, self documenting


Don't Repeat Yourself

FP encourages tiny composable methods (lego blocks) which are easy to reuse


Easy To Refactor

Tiny composable methods are easy to move around and/or remove


Concurrency

Because FP apps are pure and the side effects delegated to the edges of the app, concurrency is much easier to achieve


Where Do I Start?

{.background}


Declarative Programming

Express the logic of a computation without describing its control flow.


Declarative Programming

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}

.


Declarative Programming

// 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]

JavaScript Enablement

Not all languages can do that

{.background}


First Class Functions

Functions are values

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.


Higher Order Functions

Functions take and return functions

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.


.

{.background}


First Class Functions, Higher Order Functions

const getServerStuff = (callback) => {
  return ajaxCall((json) => {
    return callback(json)
  })
}

// ... is the same as ...

const getServerStuff = ajaxCall;

{.column}

.


Great, Thanks.

Please don't teach me JavaScript

{.background}


Okay!

Let's talk about the core paradigm of FP


Purity

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.


Purity

Basic rules:

  • No outside scope
  • Always returns a value
  • Immutability
  • Doesn't have any side effects
  • referential transparency
  • function composition

{.column}

. .


Purity: No Outside Scope

// 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

Purity: Returns a Value

// 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

Objects can’t be modified after it’s created

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.


Mutability, Shared State ❌

// 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 }

Immutability ✅

// 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 }

Mutability Es No Bueno!

Complicates debugging and reasoning about code


Benefits Of Purity

{.background}


Referential Transparency

An expression always evaluates to the same result in any context.

A spot of code is referentially transparent when it can be substituted for its evaluated value without changing the behavior of the program.


Referential Transparency

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.


Yeah Yeah Yeah

But what does this mean for my code?

{.background}


Composition

Functions Built From Functions

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: Simple Implementation

// 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

Robust compose implementation

"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

No intermediate state

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.


Pointfree

// 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!!!

Wait

Ramda concat takes two args...


Currying!

(not the food but just as tasty)

{.background}


Currying

One at a time args

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 vs Auto-Currying

// 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

Currying

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'

.

{.background}


(Real Code Demo)


Fin

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.

{.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!