CodeMash 2020: Practical Functional Programming

CodeMash 2020: 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. 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, leave this talk with a deeper understanding and appreciation for functional programming to start exploring it yourself.

94bd558238b69c45d3d3e15797ae94f7?s=128

Jeremy Fairbank

January 10, 2020
Tweet

Transcript

  1. Practical Functional Programming Jeremy Fairbank @elpapapollo

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

  3. Available in print or e-book programming-elm.com

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

  11. Pure Total Idempotent Monad Functor

  12. None
  13. 6 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 Pure
  36. None
  37. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` function detailsForOrders(orders) { let details = [] for (let i = 0; i < orders.length; i++) { details.push(orderDetails(orders[i])) } return details }
  38. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` function detailsForOrders(orders) { let details = [] for (let i = 0; i < orders.length; i++) { details.push(orderDetails(orders[i])) } return details }
  39. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` function detailsForOrders(orders) { let details = [] for (let i = 0; i < orders.length; i++) { details.push(orderDetails(orders[i])) } return details }
  40. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` function detailsForOrders(orders) { let details = [] for (let i = 0; i < orders.length; i++) { details.push(orderDetails(orders[i])) } return details }
  41. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` function detailsForOrders(orders) { let details = [] for (let i = 0; i < orders.length; i++) { details.push(orderDetails(orders[i])) } return details }
  42. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` function detailsForOrders(orders) { let details = [] for (let i = 0; i < orders.length; i++) { details.push(orderDetails(orders[i])) } return details }
  43. const url = ids => `/orders?ids=${ids.join(',')}` const orderDetails = ({

    id, date, customer }) => `Order ${id} on ${date} by ${customer}` function detailsForOrders(orders) { let details = [] for (let i = 0; i < orders.length; i++) { details.push(orderDetails(orders[i])) } return details }
  44. it('uses the correct url', () => { expect(url([1, 2, 3])).toEqual('/orders?ids=1,2,3')

    })
  45. 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', ]) })
  46. async function printOrderDetails(ids) { let orders = await fetchJson(url(ids)) let

    details = detailsForOrders(orders) for (let i = 0; i < details.length; i++) { console.log(details[i]) } }
  47. async function printOrderDetails(ids) { let orders = await fetchJson(url(ids)) let

    details = detailsForOrders(orders) for (let i = 0; i < details.length; i++) { console.log(details[i]) } }
  48. async function printOrderDetails(ids) { let orders = await fetchJson(url(ids)) let

    details = detailsForOrders(orders) for (let i = 0; i < details.length; i++) { console.log(details[i]) } }
  49. async function printOrderDetails(ids) { let orders = await fetchJson(url(ids)) let

    details = detailsForOrders(orders) for (let i = 0; i < details.length; i++) { console.log(details[i]) } }
  50. youtu.be/yTkzNHF6rMs

  51. Hard to test Hard to follow

  52. const orderDetails = ({ id, date, customer }) => `Order

    ${id} on ${date} by ${customer}` function detailsForOrders(orders) { let details = [] for (let i = 0; i < orders.length; i++) { details.push(orderDetails(orders[i])) } return details }
  53. const orderDetails = ({ id, date, customer }) => `Order

    ${id} on ${date} by ${customer}` function detailsForOrders(orders) { let details = [] for (let i = 0; i < orders.length; i++) { details.push(orderDetails(orders[i])) } return details }
  54. Imperative

  55. Declarative

  56. const orderDetails = ({ id, date, customer }) => `Order

    ${id} on ${date} by ${customer}` const detailsForOrders = orders => orders.map(orderDetails)
  57. const orderDetails = ({ id, date, customer }) => `Order

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

    2, date: '11/25/17', customer: 'Sally' } { id: 3, date: '3/30/18', customer: 'Joe' }
  59. { 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
  60. [ '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
  61. 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' }
  62. Hard to test Hard to follow Code breaks unexpectedly

  63. 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}` } }
  64. 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}` } }
  65. 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}` } }
  66. 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}` } }
  67. 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}` } }
  68. 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}` } }
  69. 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
  70. 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
  71. 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
  72. 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
  73. let hobbies = this.hobbies.splice(0, 2).join(' and ')

  74. Mutable Data

  75. Immutable Data Safety and consistency

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

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

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

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

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

  81. 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}` }
  82. 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}` }
  83. 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}` }
  84. const addHobby = (hobby, person) => Object.freeze({ ...person, hobbies: Object.freeze([...person.hobbies,

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

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

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

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

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

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

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

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

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

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

    much code
  95. const greetInEnglish = name => `Hi, ${name}` const greetInSpanish =

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    greetInEnglish = greet "Hi" greetInSpanish = greet "Hola"
  110. greetInEnglish "CodeMash" -- Hi, CodeMash greetInSpanish "CodeMash" -- Hola, CodeMash

  111. None
  112. 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 } }
  113. 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 } }
  114. 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 } }
  115. 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 } }
  116. 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 } }
  117. 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 } }
  118. 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 } }
  119. 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 } }
  120. const excitedGreeting = name => // ...

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

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('CodeMash') // Hi, CODEMASH!
  122. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('CodeMash') // Hi, CODEMASH!
  123. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('CodeMash') // Hi, CODEMASH!
  124. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('CodeMash') // Hi, CODEMASH!
  125. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('CodeMash') // Hi, CODEMASH!
  126. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('CodeMash') // Hi, CODEMASH!
  127. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('CodeMash') // Hi, CODEMASH!
  128. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('CodeMash') // Hi, CODEMASH!
  129. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('CodeMash') // Hi, CODEMASH!
  130. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('CodeMash') // Hi, CODEMASH!
  131. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('CodeMash') // Hi, CODEMASH!
  132. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('CodeMash') // Hi, CODEMASH!
  133. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, // CODEMASH greet('Hi'), exclaim, )(name) excitedGreeting('CodeMash') // Hi, CODEMASH!
  134. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), // Hi, CODEMASH exclaim, )(name) excitedGreeting('CodeMash') // Hi, CODEMASH!
  135. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, // Hi, CODEMASH! )(name) excitedGreeting('CodeMash') // Hi, CODEMASH!
  136. Function Composition

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

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "CodeMash" -- Hi, CODEMASH!
  138. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "CodeMash" -- Hi, CODEMASH!
  139. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "CodeMash" -- Hi, CODEMASH!
  140. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "CodeMash" -- Hi, CODEMASH!
  141. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper -- CODEMASH |> greet "Hi" |> exclaim excitedGreeting "CodeMash" -- Hi, CODEMASH!
  142. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" -- Hi, CODEMASH |> exclaim excitedGreeting "CodeMash" -- Hi, CODEMASH!
  143. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim -- Hi, CODEMASH! excitedGreeting "CodeMash" -- Hi, CODEMASH!
  144. 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 } }
  145. 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 } }
  146. 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 } }
  147. 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 } }
  148. 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 } }
  149. Hard to test Hard to follow Code breaks unexpectedly Too

    much code Scary to refactor
  150. 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 } }
  151. 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 } }
  152. createOrder(myShoppingCart, 'Tucker')

  153. Strong Static Types

  154. 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 }
  155. 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 }
  156. 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 }
  157. 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 }
  158. -- TYPE MISMATCH ------------------------- App.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 }
  159. -- TYPE MISMATCH ------------------------- App.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 }
  160. -- TYPE MISMATCH ------------------------- App.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 }
  161. conference : String conference = "Lambda Squared" life : Int

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

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

    life = 42 greet : String -> String greet name = "Hello, " ++ name formatCurrency : Float -> String formatCurrency amount = "$" ++ toString amount
  164. ?

  165. None
  166. Custom Types

  167. type Item = { price: number, }

  168. type Item = { price: number, }

  169. type Item = { price: number, }

  170. type Item = { price: number, } type ShoppingCart =

    { items: Item[], discounts: number[], tax: number, shippingCost: number, }
  171. type Item = { price: number, } type ShoppingCart =

    { items: Item[], discounts: number[], tax: number, shippingCost: number, }
  172. type Item = { price: number, } type ShoppingCart =

    { items: Item[], discounts: number[], tax: number, shippingCost: number, } type Order = { status: string, customer: string, total: number, }
  173. function createOrder( shoppingCart: ShoppingCart, customer: string ): Order { ...

    }
  174. function createOrder( shoppingCart: ShoppingCart, customer: string ): Order { ...

    }
  175. type Order = { status: string, customer: string, total: number,

    }
  176. type Order = { status: string, customer: string, total: number,

    }
  177. enum Status { New, Processing, Shipped, }

  178. enum Status { New, Processing, Shipped, }

  179. enum Status { New, Processing, Shipped, }

  180. enum Status { New, Processing, Shipped, } type Order =

    { status: Status, customer: string, total: number, }
  181. function createOrder( shoppingCart: ShoppingCart, customer: string ): Order { let

    total = R.pipe( addPrices(shoppingCart.items), addDiscounts(shoppingCart.discounts), addTax(shoppingCart.tax), add(shoppingCart.shippingCost), roundTotal, )(0) return { status: Status.New, customer, total } }
  182. Hard to test Hard to follow Code breaks unexpectedly Too

    much code Scary to refactor Oh, null and undefined
  183. function getBestFriendLocation(id, users) { let user = users.get(id) let friend

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

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

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

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

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

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

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

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

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

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

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

  196. type Maybe a = Nothing | Just a

  197. type Maybe a = Nothing | Just a

  198. type Maybe a = Nothing | Just a

  199. type Maybe a = Nothing | Just a

  200. 42 Just 42 "Hi" Just "Hi"

  201. Just 42 [42] Nothing [ ]

  202. import { Maybe } from 'true-myth' Maybe.just(42) // Just 42

    Maybe.nothing() // Nothing Maybe.of(42) // Just 42 Maybe.of(null) // Nothing Maybe.of(undefined) // Nothing
  203. function mapGet(key, map) { let result = map.get(key) return result

    == null ? Maybe.nothing() : Maybe.just(result) }
  204. let mapGet = Maybe.wrapReturn( (key, map) => map.get(key) )

  205. function getUserName(id, users) { let user = mapGet(id, users) if

    (user.isJust()) { return user.unsafelyUnwrap().name } }
  206. function getUserName(id, users) { let user = mapGet(id, users) if

    (user.isJust()) { return user.unsafelyUnwrap().name } } Maybe User
  207. function getUserName(id, users) { let user = mapGet(id, users) if

    (user.isJust()) { return user.unsafelyUnwrap().name } }
  208. function getUserName(id, users) { let user = mapGet(id, users) if

    (user.isJust()) { return user.unsafelyUnwrap().name } } user
  209. function getUserName(id, users) { let user = mapGet(id, users) if

    (user.isJust()) { return user.unsafelyUnwrap().name } } Nothing?
  210. function getUserName( id: Id, users: Map<Id, User> ): string {

    let user = mapGet(id, users) if (user.isJust()) { return user.unsafelyUnwrap().name } }
  211. null.ts:27:4 - error TS2366: Function lacks ending return statement and

    return type does not include 'undefined'. 27 ): string { ~~~~~~ Found 1 error. $ tsc --lib es2015 --strictNullChecks null.ts
  212. function getUserName( id: Id, users: Map<Id, User> ): string {

    let user = mapGet(id, users) return user.isJust() ? user.unsafelyUnwrap().name : '<anonymous>' }
  213. function getBestFriendLocation(id, users) { let user = users.get(id) if (user

    && user.bestFriendId) { let friend = users.get(user.bestFriendId) if (friend) { return friend.location } } }
  214. function getBestFriendLocation( id: Id, users: Map<Id, User> ): Maybe<Location> {

    let user = mapGet(id, users) if (user.isJust()) { let { bestFriendId } = user.unsafelyUnwrap() if (bestFriendId.isJust()) { let friend = mapGet(bestFriendId.unsafelyUnwrap(), users) if (friend.isJust()) { return friend.unsafelyUnwrap().location } return Maybe.nothing() } return Maybe.nothing() } return Maybe.nothing() }
  215. function getBestFriendLocation( id: Id, users: Map<Id, User> ): Maybe<Location> {

    return mapGet(id, users) .chain(user => user.bestFriendId) .chain(bestFriendId => mapGet(bestFriendId, users)) .chain(friend => friend.location) }
  216. function getBestFriendLocation( id: Id, users: Map<Id, User> ): Maybe<Location> {

    return mapGet(id, users) .chain(user => user.bestFriendId) .chain(bestFriendId => mapGet(bestFriendId, users)) .chain(friend => friend.location) }
  217. function getBestFriendLocation( id: Id, users: Map<Id, User> ): Maybe<Location> {

    return mapGet(id, users) .chain(user => user.bestFriendId) .chain(bestFriendId => mapGet(bestFriendId, users)) .chain(friend => friend.location) }
  218. function getBestFriendLocation( id: Id, users: Map<Id, User> ): Maybe<Location> {

    return mapGet(id, users) .chain(user => user.bestFriendId) .chain(bestFriendId => mapGet(bestFriendId, users)) .chain(friend => friend.location) }
  219. function getBestFriendLocation( id: Id, users: Map<Id, User> ): Maybe<Location> {

    return mapGet(id, users) .chain(user => user.bestFriendId) .chain(bestFriendId => mapGet(bestFriendId, users)) .chain(friend => friend.location) }
  220. .chain(user => user.bestFriendId)

  221. .chain(user => user.bestFriendId) .chain Nothing Nothing

  222. .chain(user => user.bestFriendId) .chain Just user ?

  223. user .chain(user => user.bestFriendId)

  224. .chain(user => user.bestFriendId) Just <id> or Nothing

  225. .chain(user => user.bestFriendId) .chain Just user Just <id> or Nothing

  226. function getBestFriendLocation( id: Id, users: Map<Id, User> ): Maybe<Location> {

    return mapGet(id, users) // Maybe<User> .chain(user => user.bestFriendId) .chain(bestFriendId => mapGet(bestFriendId, users)) .chain(friend => friend.location) }
  227. function getBestFriendLocation( id: Id, users: Map<Id, User> ): Maybe<Location> {

    return mapGet(id, users) .chain(user => user.bestFriendId) // Maybe<Id> .chain(bestFriendId => mapGet(bestFriendId, users)) .chain(friend => friend.location) }
  228. function getBestFriendLocation( id: Id, users: Map<Id, User> ): Maybe<Location> {

    return mapGet(id, users) .chain(user => user.bestFriendId) .chain(bestFriendId => mapGet(bestFriendId, users)) // Maybe<User> .chain(friend => friend.location) }
  229. function getBestFriendLocation( id: Id, users: Map<Id, User> ): Maybe<Location> {

    return mapGet(id, users) .chain(user => user.bestFriendId) .chain(bestFriendId => mapGet(bestFriendId, users)) .chain(friend => friend.location) // Maybe<Location> }
  230. function getBestFriendLocation( id: Id, users: Map<Id, User> ): Maybe<Location> {

    return mapGet(id, users) .chain(user => user.bestFriendId) .chain(bestFriendId => mapGet(bestFriendId, users)) .chain(friend => friend.location) // Maybe<Location> }
  231. P.S. You kinda, sorta just learned monads

  232. Hard to test Hard to follow Code breaks unexpectedly Too

    much code Scary to refactor Oh, null and undefined
  233. Hard to test Pure Functions Hard to follow Code breaks

    unexpectedly Too much code Scary to refactor Oh, null and undefined
  234. Hard to test Pure Functions Hard to follow Declarative Code

    Code breaks unexpectedly Too much code Scary to refactor Oh, null and undefined
  235. Code breaks unexpectedly Immutable Data Too much code Scary to

    refactor Hard to test Pure Functions Hard to follow Declarative Code Oh, null and undefined
  236. Too much code Curried, Composable Functions Scary to refactor Code

    breaks unexpectedly Immutable Data Hard to test Pure Functions Hard to follow Declarative Code Oh, null and undefined
  237. Scary to refactor Strong, Static Types Too much code Curried,

    Composable Functions Code breaks unexpectedly Immutable Data Hard to test Pure Functions Hard to follow Declarative Code Oh, null and undefined
  238. Oh, null and undefined 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 Hard to follow Declarative Code
  239. Thanks! bit.ly/cm-practical-fp Jeremy Fairbank @elpapapollo