All Things Open 2018: Practical Functional Programming

All Things Open 2018: Practical Functional Programming

Functional programming is more than just math and monads. Functional programming empowers developers to solve real problems with safe, predictable, and maintainable code. In this talk, discover the basics of functional programming and how to apply functional concepts in a practical manner. You will learn how pure functions are easily testable, how to compose specialized functions to create more complex functions, how immutable data prevents bugs, how to prevent runtime errors with static types, how to safely model nulls with special types, and more! Most importantly, you will leave this talk with a deeper understanding and appreciation for functional programming so you can start exploring it yourself.

94bd558238b69c45d3d3e15797ae94f7?s=128

Jeremy Fairbank

October 22, 2018
Tweet

Transcript

  1. Practical Functional Programming Jeremy Fairbank @elpapapollo

  2. @testdouble helps improves how the world build software. testdouble.com

  3. bit.ly/programming-elm

  4. Functional programming:

  5. Pure Functional programming:

  6. Pure Total Functional programming:

  7. Pure Total Idempotent Functional programming:

  8. Pure Total Idempotent Monad Functional programming:

  9. Pure Total Idempotent Monad Functor Functional programming:

  10. Easier-to-reason-about- oriented programming

  11. Pure Total Idempotent Monad Functor

  12. None
  13. 5 Become a rockstar programmer with these tricks!

  14. Hard to test

  15. async function printOrderDetails(ids) { let url = `/orders?ids=${ids.join(',')}` let orders

    = await fetchJson(url) for (let i = 0; i < orders.length; i++) { let { id, date, customer } = orders[i] let details = `Order ${id} on ${date} by ${customer}` console.log(details) } }
  16. async function printOrderDetails(ids) { let url = `/orders?ids=${ids.join(',')}` let orders

    = await fetchJson(url) for (let i = 0; i < orders.length; i++) { let { id, date, customer } = orders[i] let details = `Order ${id} on ${date} by ${customer}` console.log(details) } }
  17. async function printOrderDetails(ids) { let url = `/orders?ids=${ids.join(',')}` let orders

    = await fetchJson(url) for (let i = 0; i < orders.length; i++) { let { id, date, customer } = orders[i] let details = `Order ${id} on ${date} by ${customer}` console.log(details) } }
  18. async function printOrderDetails(ids) { let url = `/orders?ids=${ids.join(',')}` let orders

    = await fetchJson(url) for (let i = 0; i < orders.length; i++) { let { id, date, customer } = orders[i] let details = `Order ${id} on ${date} by ${customer}` console.log(details) } }
  19. async function printOrderDetails(ids) { let url = `/orders?ids=${ids.join(',')}` let orders

    = await fetchJson(url) for (let i = 0; i < orders.length; i++) { let { id, date, customer } = orders[i] let details = `Order ${id} on ${date} by ${customer}` console.log(details) } }
  20. const td = require('testdouble') describe('order details', () => { let

    originalLog = console.log let printOrderDetails let fetchJson beforeEach(() => { fetchJson = td.replace('./fetchJson') console.log = td.function('console.log') printOrderDetails = require('./printOrderDetails') }) afterEach(() => { td.reset() console.log = originalLog })
  21. const td = require('testdouble') describe('order details', () => { let

    originalLog = console.log let printOrderDetails let fetchJson beforeEach(() => { fetchJson = td.replace('./fetchJson') console.log = td.function('console.log') printOrderDetails = require('./printOrderDetails') }) afterEach(() => { td.reset() console.log = originalLog })
  22. const td = require('testdouble') describe('order details', () => { let

    originalLog = console.log let printOrderDetails let fetchJson beforeEach(() => { fetchJson = td.replace('./fetchJson') console.log = td.function('console.log') printOrderDetails = require('./printOrderDetails') }) afterEach(() => { td.reset() console.log = originalLog })
  23. const td = require('testdouble') describe('order details', () => { let

    originalLog = console.log let printOrderDetails let fetchJson beforeEach(() => { fetchJson = td.replace('./fetchJson') console.log = td.function('console.log') printOrderDetails = require('./printOrderDetails') }) afterEach(() => { td.reset() console.log = originalLog })
  24. it('prints details for multiple orders', async () => { td

    .when(fetchJson('/orders?ids=1,2,3')) .thenResolve([ { id: 1, date: '12/1/17', customer: 'Tucker' }, { id: 2, date: '11/25/17', customer: 'Sally' }, { id: 3, date: '3/30/18', customer: 'Joe' }, ]) await printOrderDetails([1, 2, 3]) td.verify(console.log('Order 1 on 12/1/17 by Tucker')) td.verify(console.log('Order 2 on 11/25/17 by Sally')) td.verify(console.log('Order 3 on 3/30/18 by Joe')) }) })
  25. it('prints details for multiple orders', async () => { td

    .when(fetchJson('/orders?ids=1,2,3')) .thenResolve([ { id: 1, date: '12/1/17', customer: 'Tucker' }, { id: 2, date: '11/25/17', customer: 'Sally' }, { id: 3, date: '3/30/18', customer: 'Joe' }, ]) await printOrderDetails([1, 2, 3]) td.verify(console.log('Order 1 on 12/1/17 by Tucker')) td.verify(console.log('Order 2 on 11/25/17 by Sally')) td.verify(console.log('Order 3 on 3/30/18 by Joe')) }) })
  26. it('prints details for multiple orders', async () => { td

    .when(fetchJson('/orders?ids=1,2,3')) .thenResolve([ { id: 1, date: '12/1/17', customer: 'Tucker' }, { id: 2, date: '11/25/17', customer: 'Sally' }, { id: 3, date: '3/30/18', customer: 'Joe' }, ]) await printOrderDetails([1, 2, 3]) td.verify(console.log('Order 1 on 12/1/17 by Tucker')) td.verify(console.log('Order 2 on 11/25/17 by Sally')) td.verify(console.log('Order 3 on 3/30/18 by Joe')) }) })
  27. it('prints details for multiple orders', async () => { td

    .when(fetchJson('/orders?ids=1,2,3')) .thenResolve([ { id: 1, date: '12/1/17', customer: 'Tucker' }, { id: 2, date: '11/25/17', customer: 'Sally' }, { id: 3, date: '3/30/18', customer: 'Joe' }, ]) await printOrderDetails([1, 2, 3]) td.verify(console.log('Order 1 on 12/1/17 by Tucker')) td.verify(console.log('Order 2 on 11/25/17 by Sally')) td.verify(console.log('Order 3 on 3/30/18 by Joe')) }) })
  28. Side Effects

  29. async function printOrderDetails(ids) { let url = `/orders?ids=${ids.join(',')}` let orders

    = await fetchJson(url) for (let i = 0; i < orders.length; i++) { let { id, date, customer } = orders[i] let details = `Order ${id} on ${date} by ${customer}` console.log(details) } }
  30. async function printOrderDetails(ids) { let url = `/orders?ids=${ids.join(',')}` let orders

    = await fetchJson(url) for (let i = 0; i < orders.length; i++) { let { id, date, customer } = orders[i] let details = `Order ${id} on ${date} by ${customer}` console.log(details) } }
  31. async function printOrderDetails(ids) { let url = `/orders?ids=${ids.join(',')}` let orders

    = await fetchJson(url) for (let i = 0; i < orders.length; i++) { let { id, date, customer } = orders[i] let details = `Order ${id} on ${date} by ${customer}` console.log(details) } }
  32. Less Predictable

  33. Harder to Test

  34. Functions

  35. const add = (x, y) => x + y add(2,

    3) === 5 add(2, 3) === 5 add(2, 3) === 5
  36. None
  37. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` const detailsForOrders = orders => orders.map(orderDetails)
  38. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` const detailsForOrders = orders => orders.map(orderDetails)
  39. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` const detailsForOrders = orders => orders.map(orderDetails)
  40. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` const detailsForOrders = orders => orders.map(orderDetails)
  41. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` const detailsForOrders = orders => orders.map(orderDetails)
  42. { id: 1, date: '12/1/17', customer: 'Tucker' } { id:

    2, date: '11/25/17', customer: 'Sally' } { id: 3, date: '3/30/18', customer: 'Joe' }
  43. { id: 1, date: '12/1/17', customer: 'Tucker' } { id:

    2, date: '11/25/17', customer: 'Sally' } { id: 3, date: '3/30/18', customer: 'Joe' } [ 'Order 1 on 12/1/17 by Tucker', ] map orderDetails
  44. [ 'Order 1 on 12/1/17 by Tucker', 'Order 2 on

    11/25/17 by Sally', ] orderDetails { id: 1, date: '12/1/17', customer: 'Tucker' } { id: 2, date: '11/25/17', customer: 'Sally' } { id: 3, date: '3/30/18', customer: 'Joe' } map
  45. orderDetails [ 'Order 1 on 12/1/17 by Tucker', 'Order 2

    on 11/25/17 by Sally', 'Order 3 on 3/30/18 by Joe', ] map { id: 1, date: '12/1/17', customer: 'Tucker' } { id: 2, date: '11/25/17', customer: 'Sally' } { id: 3, date: '3/30/18', customer: 'Joe' }
  46. it('uses the correct url', () => { expect(url([1, 2, 3])).toEqual('/orders?ids=1,2,3')

    })
  47. it('creates details for orders', () => { let orders =

    [ { id: 1, date: '12/1/17', customer: 'Tucker' }, { id: 2, date: '11/25/17', customer: 'Sally' }, { id: 3, date: '3/30/18', customer: 'Joe' }, ] expect(detailsForOrders(orders)).toEqual([ 'Order 1 on 12/1/17 by Tucker', 'Order 2 on 11/25/17 by Sally', 'Order 3 on 3/30/18 by Joe', ]) })
  48. async function printOrderDetails(ids) { let orders = await fetchJson(url(ids)) let

    details = detailsForOrders(orders) details.forEach(orderDetails => { console.log(orderDetails) }) }
  49. Hard to test Code breaks unexpectedly

  50. class Person { constructor(name, hobbies) { this.name = name this.hobbies

    = hobbies } describe() { if (this.hobbies.length === 0) { return `${this.name} doesn't do much` } let hobbies = this.hobbies.splice(0, 2).join(' and ') return `${this.name} likes to do things such as ${hobbies}` } }
  51. class Person { constructor(name, hobbies) { this.name = name this.hobbies

    = hobbies } describe() { if (this.hobbies.length === 0) { return `${this.name} doesn't do much` } let hobbies = this.hobbies.splice(0, 2).join(' and ') return `${this.name} likes to do things such as ${hobbies}` } }
  52. class Person { constructor(name, hobbies) { this.name = name this.hobbies

    = hobbies } describe() { if (this.hobbies.length === 0) { return `${this.name} doesn't do much` } let hobbies = this.hobbies.splice(0, 2).join(' and ') return `${this.name} likes to do things such as ${hobbies}` } }
  53. class Person { constructor(name, hobbies) { this.name = name this.hobbies

    = hobbies } describe() { if (this.hobbies.length === 0) { return `${this.name} doesn't do much` } let hobbies = this.hobbies.splice(0, 2).join(' and ') return `${this.name} likes to do things such as ${hobbies}` } }
  54. let person = new Person('Jeremy', [ 'programming', 'reading', 'playing music',

    ]) person.describe() // Jeremy likes to do things such as programming and reading person.describe() // Jeremy likes to do things such as playing music
  55. let person = new Person('Jeremy', [ 'programming', 'reading', 'playing music',

    ]) person.describe() // Jeremy likes to do things such as programming and reading person.describe() // Jeremy likes to do things such as playing music
  56. let person = new Person('Jeremy', [ 'programming', 'reading', 'playing music',

    ]) person.describe() // Jeremy likes to do things such as programming and reading person.describe() // Jeremy likes to do things such as playing music
  57. let person = new Person('Jeremy', [ 'programming', 'reading', 'playing music',

    ]) person.describe() // Jeremy likes to do things such as programming and reading person.describe() // Jeremy likes to do things such as playing music
  58. let hobbies = this.hobbies.splice(0, 2).join(' and ')

  59. Mutable Data

  60. Immutable Data Safety and consistency

  61. class Person { constructor(name, hobbies) { this.name = name this.hobbies

    = Object.freeze(hobbies) } // ... }
  62. class Person { // ... describe() { // ... let

    hobbies = this.hobbies.splice(0, 2).join(' and ') // ... } }
  63. class Person { // ... describe() { // ... let

    hobbies = this.hobbies.splice(0, 2).join(' and ') // ... } } TypeError: Cannot add/remove sealed array elements
  64. class Person { // ... describe() { // ... let

    hobbies = this.hobbies.slice(0, 2).join(' and ') // ... } }
  65. Create New State

  66. const create = (name, hobbies) => Object.freeze({ name, hobbies: Object.freeze(hobbies)

    }) function describe(person) { if (person.hobbies.length === 0) { return `${person.name} doesn't do much` } let hobbies = person.hobbies.slice(0, 2).join(' and ') return `${person.name} likes to do things such as ${hobbies}` }
  67. const create = (name, hobbies) => Object.freeze({ name, hobbies: Object.freeze(hobbies)

    }) function describe(person) { if (person.hobbies.length === 0) { return `${person.name} doesn't do much` } let hobbies = person.hobbies.slice(0, 2).join(' and ') return `${person.name} likes to do things such as ${hobbies}` }
  68. const create = (name, hobbies) => Object.freeze({ name, hobbies: Object.freeze(hobbies)

    }) function describe(person) { if (person.hobbies.length === 0) { return `${person.name} doesn't do much` } let hobbies = person.hobbies.slice(0, 2).join(' and ') return `${person.name} likes to do things such as ${hobbies}` }
  69. const addHobby = (hobby, person) => Object.freeze({ ...person, hobbies: Object.freeze([...person.hobbies,

    hobby]), })
  70. const addHobby = (hobby, person) => Object.freeze({ ...person, hobbies: Object.freeze([...person.hobbies,

    hobby]), })
  71. const addHobby = (hobby, person) => Object.freeze({ ...person, hobbies: Object.freeze([...person.hobbies,

    hobby]), })
  72. const addHobby = (hobby, person) => Object.freeze({ ...person, hobbies: Object.freeze([...person.hobbies,

    hobby]), })
  73. const addHobby = (hobby, person) => Object.freeze({ ...person, hobbies: Object.freeze([...person.hobbies,

    hobby]), })
  74. let person = create('Jeremy', []) person = addHobby('programming', person) person

    = addHobby('reading', person) describe(person) // Jeremy likes to do things such as programming and reading
  75. let person = create('Jeremy', []) person = addHobby('programming', person) person

    = addHobby('reading', person) describe(person) // Jeremy likes to do things such as programming and reading
  76. let person = create('Jeremy', []) person = addHobby('programming', person) person

    = addHobby('reading', person) describe(person) // Jeremy likes to do things such as programming and reading
  77. let person = create('Jeremy', []) person = addHobby('programming', person) person

    = addHobby('reading', person) describe(person) // Jeremy likes to do things such as programming and reading
  78. let person = create('Jeremy', []) person = addHobby('programming', person) person

    = addHobby('reading', person) describe(person) // Jeremy likes to do things such as programming and reading
  79. Hard to test Code breaks unexpectedly Too much code

  80. const greetInEnglish = name => `Hi, ${name}` const greetInSpanish =

    name => `Hola, ${name}`
  81. const greet = (greeting, name) => `${greeting}, ${name}` // ..

    const greetInEnglish = name => greet('Hi', name) const greetInSpanish = name => greet('Hola', name)
  82. const greet = greeting => name => `${greeting}, ${name}` const

    greetInEnglish = greet('Hi') const greetInSpanish = greet('Hola') greetInEnglish('All Things Open') // Hi, All Things Open greetInSpanish('All Things Open') // Hola, All Things Open
  83. const greet = greeting => name => `${greeting}, ${name}` const

    greetInEnglish = greet('Hi') const greetInSpanish = greet('Hola') greetInEnglish('All Things Open') // Hi, All Things Open greetInSpanish('All Things Open') // Hola, All Things Open
  84. const greet = greeting => name => `${greeting}, ${name}` const

    greetInEnglish = greet('Hi') const greetInSpanish = greet('Hola') greetInEnglish('All Things Open') // Hi, All Things Open greetInSpanish('All Things Open') // Hola, All Things Open
  85. const greet = greeting => name => `${greeting}, ${name}` const

    greetInEnglish = greet('Hi') const greetInSpanish = greet('Hola') greetInEnglish('All Things Open') // Hi, All Things Open greetInSpanish('All Things Open') // Hola, All Things Open
  86. const greet = greeting => name => `${greeting}, ${name}` const

    greetInEnglish = greet('Hi') const greetInSpanish = greet('Hola') greetInEnglish('All Things Open') // Hi, All Things Open greetInSpanish('All Things Open') // Hola, All Things Open
  87. const greet = greeting => name => `${greeting}, ${name}` const

    greetInEnglish = greet('Hi') const greetInSpanish = greet('Hola') greetInEnglish('All Things Open') // Hi, All Things Open greetInSpanish('All Things Open') // Hola, All Things Open
  88. const greet = greeting => name => `${greeting}, ${name}` const

    greetInEnglish = greet('Hi') const greetInSpanish = greet('Hola') greetInEnglish('All Things Open') // Hi, All Things Open greetInSpanish('All Things Open') // Hola, All Things Open
  89. const greet = greeting => name => `${greeting}, ${name}` const

    greetInEnglish = greet('Hi') const greetInSpanish = greet('Hola') greetInEnglish('All Things Open') // Hi, All Things Open greetInSpanish('All Things Open') // Hola, All Things Open
  90. const greet = greeting => name => `${greeting}, ${name}` const

    greetInEnglish = greet('Hi') const greetInSpanish = greet('Hola') Curried Function Partial Application
  91. greet greeting name = greeting ++ ", " ++ name

    greetInEnglish = greet "Hi" greetInSpanish = greet "Hola"
  92. greet greeting name = greeting ++ ", " ++ name

    greetInEnglish = greet "Hi" greetInSpanish = greet "Hola"
  93. greet greeting name = greeting ++ ", " ++ name

    greetInEnglish = greet "Hi" greetInSpanish = greet "Hola"
  94. greet greeting name = greeting ++ ", " ++ name

    greetInEnglish = greet "Hi" greetInSpanish = greet "Hola"
  95. greetInEnglish "All Things Open" -- Hi, All Things Open greetInSpanish

    "All Things Open" -- Hola, All Things Open
  96. None
  97. function createOrder(shoppingCart, customer) { let total = 0 shoppingCart.items.forEach(item =>

    { total += item.price }) shoppingCart.discounts.forEach(discount => { total *= 1 - discount }) total *= 1 + shoppingCart.tax total += shoppingCart.shippingCost total = Math.round(total * 100) / 100 return { status: 'new', customer, total } }
  98. function createOrder(shoppingCart, customer) { let total = 0 shoppingCart.items.forEach(item =>

    { total += item.price }) shoppingCart.discounts.forEach(discount => { total *= 1 - discount }) total *= 1 + shoppingCart.tax total += shoppingCart.shippingCost total = Math.round(total * 100) / 100 return { status: 'new', customer, total } }
  99. function createOrder(shoppingCart, customer) { let total = 0 shoppingCart.items.forEach(item =>

    { total += item.price }) shoppingCart.discounts.forEach(discount => { total *= 1 - discount }) total *= 1 + shoppingCart.tax total += shoppingCart.shippingCost total = Math.round(total * 100) / 100 return { status: 'new', customer, total } }
  100. function createOrder(shoppingCart, customer) { let total = 0 shoppingCart.items.forEach(item =>

    { total += item.price }) shoppingCart.discounts.forEach(discount => { total *= 1 - discount }) total *= 1 + shoppingCart.tax total += shoppingCart.shippingCost total = Math.round(total * 100) / 100 return { status: 'new', customer, total } }
  101. function createOrder(shoppingCart, customer) { let total = 0 shoppingCart.items.forEach(item =>

    { total += item.price }) shoppingCart.discounts.forEach(discount => { total *= 1 - discount }) total *= 1 + shoppingCart.tax total += shoppingCart.shippingCost total = Math.round(total * 100) / 100 return { status: 'new', customer, total } }
  102. function createOrder(shoppingCart, customer) { let total = 0 shoppingCart.items.forEach(item =>

    { total += item.price }) shoppingCart.discounts.forEach(discount => { total *= 1 - discount }) total *= 1 + shoppingCart.tax total += shoppingCart.shippingCost total = Math.round(total * 100) / 100 return { status: 'new', customer, total } }
  103. function createOrder(shoppingCart, customer) { let total = 0 shoppingCart.items.forEach(item =>

    { total += item.price }) shoppingCart.discounts.forEach(discount => { total *= 1 - discount }) total *= 1 + shoppingCart.tax total += shoppingCart.shippingCost total = Math.round(total * 100) / 100 return { status: 'new', customer, total } }
  104. function createOrder(shoppingCart, customer) { let total = 0 shoppingCart.items.forEach(item =>

    { total += item.price }) shoppingCart.discounts.forEach(discount => { total *= 1 - discount }) total *= 1 + shoppingCart.tax total += shoppingCart.shippingCost total = Math.round(total * 100) / 100 return { status: 'new', customer, total } }
  105. const excitedGreeting = name => // ...

  106. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  107. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  108. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  109. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  110. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  111. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  112. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  113. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  114. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  115. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  116. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  117. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  118. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, // ALL THINGS OPEN greet('Hi'), exclaim, )(name) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  119. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), // Hi, ALL THINGS OPEN exclaim, )(name) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  120. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, // Hi, ALL THINGS OPEN! )(name) excitedGreeting('All Things Open') // Hi, ALL THINGS OPEN!
  121. Function Composition

  122. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "All Things Open" -- Hi, ALL THINGS OPEN!
  123. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "All Things Open" -- Hi, ALL THINGS OPEN!
  124. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "All Things Open" -- Hi, ALL THINGS OPEN!
  125. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "All Things Open" -- Hi, ALL THINGS OPEN!
  126. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper -- ALL THINGS OPEN |> greet "Hi" |> exclaim excitedGreeting "All Things Open" -- Hi, ALL THINGS OPEN!
  127. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" -- Hi, ALL THINGS OPEN |> exclaim excitedGreeting "All Things Open" -- Hi, ALL THINGS OPEN!
  128. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim -- Hi, ALL THINGS OPEN! excitedGreeting "All Things Open" -- Hi, ALL THINGS OPEN!
  129. function createOrder(shoppingCart, customer) { let total = 0 shoppingCart.items.forEach(item =>

    { total += item.price }) shoppingCart.discounts.forEach(discount => { total *= 1 - discount }) total *= 1 + shoppingCart.tax total += shoppingCart.shippingCost total = Math.round(total * 100) / 100 return { status: 'new', customer, total } }
  130. function createOrder(shoppingCart, customer) { let total = R.pipe( addPrices(shoppingCart.items), addDiscounts(shoppingCart.discounts),

    addTax(shoppingCart.tax), add(shoppingCart.shippingCost), roundTotal, )(0) return { status: 'new', customer, total } }
  131. function createOrder(shoppingCart, customer) { let total = R.pipe( addPrices(shoppingCart.items), addDiscounts(shoppingCart.discounts),

    addTax(shoppingCart.tax), add(shoppingCart.shippingCost), roundTotal, )(0) return { status: 'new', customer, total } }
  132. function createOrder(shoppingCart, customer) { let total = R.pipe( addPrices(shoppingCart.items), addDiscounts(shoppingCart.discounts),

    addTax(shoppingCart.tax), add(shoppingCart.shippingCost), roundTotal, )(0) return { status: 'new', customer, total } }
  133. function createOrder(shoppingCart, customer) { let total = R.pipe( addPrices(shoppingCart.items), addDiscounts(shoppingCart.discounts),

    addTax(shoppingCart.tax), add(shoppingCart.shippingCost), roundTotal, )(0) return { status: 'new', customer, total } }
  134. Hard to test Code breaks unexpectedly Too much code Scary

    to refactor
  135. function createOrder(shoppingCart, customer) { let total = R.pipe( addPrices(shoppingCart.items), addDiscounts(shoppingCart.discounts),

    addTax(shoppingCart.tax), add(shoppingCart.shippingCost), roundTotal, )(0) return { status: 'new', customer, total } }
  136. const createOrder = customer => shoppingCart => { let total

    = R.pipe( addPrices(shoppingCart.items), addDiscounts(shoppingCart.discounts), addTax(shoppingCart.tax), add(shoppingCart.shippingCost), roundTotal, )(0) return { status: 'new', customer, total } }
  137. createOrder(myShoppingCart, 'Tucker')

  138. Strong Static Types

  139. createOrder shoppingCart customer = let total = addPrices shoppingCart.items 0

    |> addDiscounts shoppingCart.discounts |> addTax shoppingCart.tax |> add shoppingCart.shippingCost |> roundTotal in { status = "new" , customer = customer , total = total }
  140. createOrder shoppingCart customer = let total = addPrices shoppingCart.items 0

    |> addDiscounts shoppingCart.discounts |> addTax shoppingCart.tax |> add shoppingCart.shippingCost |> roundTotal in { status = "new" , customer = customer , total = total }
  141. createOrder shoppingCart customer = let total = addPrices shoppingCart.items 0

    |> addDiscounts shoppingCart.discounts |> addTax shoppingCart.tax |> add shoppingCart.shippingCost |> roundTotal in { status = "new" , customer = customer , total = total }
  142. createOrder customer shoppingCart = let total = addPrices shoppingCart.items 0

    |> addDiscounts shoppingCart.discounts |> addTax shoppingCart.tax |> add shoppingCart.shippingCost |> roundTotal in { status = "new" , customer = customer , total = total }
  143. -- TYPE MISMATCH ------------------------- Responsibilities.elm The 2nd argument to `createOrder`

    is not what I expect: 66| createOrder myShoppingCart "Tucker" ^^^^^^^^ This argument is a string of type: String But `createOrder` needs the 2nd argument to be: { b | discounts : List Float , items : List { a | price : Float } , shippingCost : Float , tax : Float }
  144. -- TYPE MISMATCH ------------------------- Responsibilities.elm The 2nd argument to `createOrder`

    is not what I expect: 66| createOrder myShoppingCart "Tucker" ^^^^^^^^ This argument is a string of type: String But `createOrder` needs the 2nd argument to be: { b | discounts : List Float , items : List { a | price : Float } , shippingCost : Float , tax : Float }
  145. -- TYPE MISMATCH ------------------------- Responsibilities.elm The 2nd argument to `createOrder`

    is not what I expect: 66| createOrder myShoppingCart "Tucker" ^^^^^^^^ This argument is a string of type: String But `createOrder` needs the 2nd argument to be: { b | discounts : List Float , items : List { a | price : Float } , shippingCost : Float , tax : Float }
  146. conference : String conference = "Lambda Squared" life : Int

    life = 42 greet : String -> String greet name = "Hello, " ++ name formatCurrency : Float -> String formatCurrency amount = "$" ++ String.fromFloat amount
  147. conference : String conference = "Lambda Squared" life : Int

    life = 42 greet : String -> String greet name = "Hello, " ++ name formatCurrency : Float -> String formatCurrency amount = "$" ++ String.fromFloat amount
  148. conference : String conference = "Lambda Squared" life : Int

    life = 42 greet : String -> String greet name = "Hello, " ++ name formatCurrency : Float -> String formatCurrency amount = "$" ++ String.fromFloat amount
  149. Custom Types

  150. type alias Item = { price : Float }

  151. type alias Item = { price : Float }

  152. type alias Item = { price : Float }

  153. type alias Item = { price : Float } type

    alias ShoppingCart = { items : List Item , discounts : List Float , tax : Float , shippingCost : Float }
  154. type alias Item = { price : Float } type

    alias ShoppingCart = { items : List Item , discounts : List Float , tax : Float , shippingCost : Float }
  155. type alias Item = { price : Float } type

    alias ShoppingCart = { items : List Item , discounts : List Float , tax : Float , shippingCost : Float } type alias Order = { status : String , customer : String , total : Float }
  156. createOrder : ShoppingCart -> String -> Order createOrder shoppingCart customer

    = ...
  157. createOrder : ShoppingCart -> String -> Order createOrder shoppingCart customer

    = ...
  158. type alias Order = { status : String , customer

    : String , total : Float }
  159. type alias Order = { status : String , customer

    : String , total : Float }
  160. type Status = New | Processing | Shipped

  161. type Status = New | Processing | Shipped

  162. type Status = New | Processing | Shipped

  163. type Status = New | Processing | Shipped type alias

    Order = { status : Status , customer : String , total : Float }
  164. createOrder : ShoppingCart -> String -> Order createOrder shoppingCart customer

    = let total = addPrices shoppingCart.items 0 |> addDiscounts shoppingCart.discounts |> addTax shoppingCart.tax |> add shoppingCart.shippingCost |> roundTotal in { status = New , customer = customer , total = total }
  165. Hard to test Code breaks unexpectedly Too much code Scary

    to refactor Oh, null
  166. function getBestFriendLocation(id, users) { let user = users.get(id) let friend

    = users.get(user.bestFriendId) return friend.location }
  167. function getBestFriendLocation(id, users) { let user = users.get(id) let friend

    = users.get(user.bestFriendId) return friend.location }
  168. function getBestFriendLocation(id, users) { let user = users.get(id) let friend

    = users.get(user.bestFriendId) return friend.location }
  169. function getBestFriendLocation(id, users) { let user = users.get(id) let friend

    = users.get(user.bestFriendId) return friend.location }
  170. getBestFriendLocation(42, users) Uncaught TypeError: Cannot read property 'bestFriendId' of null

  171. function getBestFriendLocation(id, users) { let user = users.get(id) let friend

    = users.get(user.bestFriendId) return friend.location } null
  172. function getBestFriendLocation(id, users) { let user = users.get(id) if (user)

    { let friend = users.get(user.bestFriendId) return friend.location } }
  173. function getBestFriendLocation(id, users) { let user = users.get(id) if (user)

    { let friend = users.get(user.bestFriendId) return friend.location } } null
  174. function getBestFriendLocation(id, users) { let user = users.get(id) if (user

    && user.bestFriendId) { let friend = users.get(user.bestFriendId) return friend.location } }
  175. function getBestFriendLocation(id, users) { let user = users.get(id) if (user

    && user.bestFriendId) { let friend = users.get(user.bestFriendId) return friend.location } } null
  176. function getBestFriendLocation(id, users) { let user = users.get(id) if (user

    && user.bestFriendId) { let friend = users.get(user.bestFriendId) if (friend) { return friend.location } } }
  177. None
  178. if () {} if () {} if () {}

  179. type Maybe a = Nothing | Just a

  180. type Maybe a = Nothing | Just a

  181. type Maybe a = Nothing | Just a

  182. type Maybe a = Nothing | Just a

  183. 42 Just 42 "Hi" Just "Hi"

  184. Just 42 [42] Nothing [ ]

  185. getUserName : Id -> Dict Id User -> String getUserName

    id users = Dict.get id users
  186. getUserName : Id -> Dict Id User -> String getUserName

    id users = Dict.get id users
  187. getUserName : Id -> Dict Id User -> String getUserName

    id users = Dict.get id users
  188. getUserName : Id -> Dict Id User -> String getUserName

    id users = Dict.get id users
  189. getUserName : Id -> Dict Id User -> String getUserName

    id users = Dict.get id users Maybe User
  190. getUserName : Id -> Dict Id User -> String getUserName

    id users = case Dict.get id users of Just user -> user.name
  191. getUserName : Id -> Dict Id User -> String getUserName

    id users = case Dict.get id users of Just user -> user.name
  192. getUserName : Id -> Dict Id User -> String getUserName

    id users = case Dict.get id users of Just user -> user.name user
  193. getUserName : Id -> Dict Id User -> String getUserName

    id users = case Dict.get id users of Just user -> user.name
  194. -- MISSING PATTERNS ------------------------------ App.elm This `case` does not have

    branches for all possibilities: 31|> case Dict.get id users of 32|> Just user -> 33|> user.name Missing possibilities include: Nothing I would have to crash if I saw one of those. Add branches for them!
  195. getUserName : Id -> Dict Id User -> String getUserName

    id users = case Dict.get id users of Just user -> user.name Nothing -> "<anonymous>"
  196. function getBestFriendLocation(id, users) { let user = users.get(id) if (user

    && user.bestFriendId) { let friend = users.get(user.bestFriendId) if (friend) { return friend.location } } }
  197. getBestFriendLocation : Id -> Dict Id User -> Maybe Location

    getBestFriendLocation id users = case Dict.get id users of Just user -> case user.bestFriendId of Just bestFriendId -> case Dict.get bestFriendId users of Just friend -> friend.location Nothing -> Nothing Nothing -> Nothing Nothing -> Nothing
  198. getBestFriendLocation id users = Dict.get id users |> Maybe.andThen (\user

    -> user.bestFriendId) |> Maybe.andThen (\fid -> Dict.get fid users) |> Maybe.andThen (\friend -> friend.location)
  199. getBestFriendLocation id users = Dict.get id users |> Maybe.andThen (\user

    -> user.bestFriendId) |> Maybe.andThen (\fid -> Dict.get fid users) |> Maybe.andThen (\friend -> friend.location)
  200. getBestFriendLocation id users = Dict.get id users |> Maybe.andThen (\user

    -> user.bestFriendId) |> Maybe.andThen (\fid -> Dict.get fid users) |> Maybe.andThen (\friend -> friend.location)
  201. getBestFriendLocation id users = Dict.get id users |> Maybe.andThen (\user

    -> user.bestFriendId) |> Maybe.andThen (\fid -> Dict.get fid users) |> Maybe.andThen (\friend -> friend.location)
  202. getBestFriendLocation id users = Dict.get id users |> Maybe.andThen (\user

    -> user.bestFriendId) |> Maybe.andThen (\fid -> Dict.get fid users) |> Maybe.andThen (\friend -> friend.location)
  203. getBestFriendLocation id users = Dict.get id users |> Maybe.andThen (\user

    -> user.bestFriendId) |> Maybe.andThen (\fid -> Dict.get fid users) |> Maybe.andThen (\friend -> friend.location)
  204. getBestFriendLocation id users = Dict.get id users |> Maybe.andThen (\user

    -> user.bestFriendId) |> Maybe.andThen (\fid -> Dict.get fid users) |> Maybe.andThen (\friend -> friend.location)
  205. getBestFriendLocation id users = Dict.get id users |> Maybe.andThen (\user

    -> user.bestFriendId) |> Maybe.andThen (\fid -> Dict.get fid users) |> Maybe.andThen (\friend -> friend.location)
  206. P.S. You kinda, sorta just learned monads

  207. Hard to test Code breaks unexpectedly Too much code Scary

    to refactor Oh, null
  208. Hard to test Pure Functions Code breaks unexpectedly Too much

    code Scary to refactor Oh, null
  209. Code breaks unexpectedly Immutable Data Too much code Scary to

    refactor Oh, null Hard to test Pure Functions
  210. Too much code Curried, Composable Functions Scary to refactor Oh,

    null Code breaks unexpectedly Immutable Data Hard to test Pure Functions
  211. Scary to refactor Strong, Static Types Oh, null Too much

    code Curried, Composable Functions Code breaks unexpectedly Immutable Data Hard to test Pure Functions
  212. Oh, null Maybe and Monads Scary to refactor Strong, Static

    Types Too much code Curried, Composable Functions Code breaks unexpectedly Immutable Data Hard to test Pure Functions
  213. Thanks! bit.ly/ato-practical-fp Jeremy Fairbank @elpapapollo