Lambda Squared 2018: Practical Functional Programming

Lambda Squared 2018: Practical Functional Programming

94bd558238b69c45d3d3e15797ae94f7?s=128

Jeremy Fairbank

March 30, 2018
Tweet

Transcript

  1. Practical Functional Programming Jeremy Fairbank @elpapapollo

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

  3. In beta now! 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. 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
  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. 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
  68. 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
  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 hobbies = this.hobbies.splice(0, 2).join(' and ')

  72. Mutable Data

  73. Immutable Data Safety and consistency

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

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

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

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

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

  79. 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}` }
  80. 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}` }
  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 addHobby = (hobby, person) => Object.freeze({ ...person, hobbies: Object.freeze([...person.hobbies,

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

    hobby]), })
  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. let person = create('Jeremy', []) person = addHobby('programming', person) person

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

    = addHobby('reading', person) describe(person) // Jeremy likes to do things such as programming and reading
  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. Hard to test Hard to follow Code breaks unexpectedly Too

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

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

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

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

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

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

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

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

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

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

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

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

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

    greetInEnglish = greet "Hi" greetInSpanish = greet "Hola"
  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. greetInEnglish "Lambda Squared" -- Hi, Lambda Squared greetInSpanish "Lambda Squared"

    -- Hola, Lambda Squared
  109. None
  110. 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 } }
  111. 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 } }
  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. const excitedGreeting = name => // ...

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

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('Lambda Squared') // Hi, LAMBDA SQUARED!
  120. const toUpper = string => string.toUpperCase() const greet = greeting

    => name => `${greeting}, ${name}` const exclaim = phrase => `${phrase}!` const excitedGreeting = name => exclaim(greet('Hi')(toUpper(name))) excitedGreeting('Lambda Squared') // Hi, LAMBDA SQUARED!
  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('Lambda Squared') // Hi, LAMBDA SQUARED!
  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('Lambda Squared') // Hi, LAMBDA SQUARED!
  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('Lambda Squared') // Hi, LAMBDA SQUARED!
  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('Lambda Squared') // Hi, LAMBDA SQUARED!
  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('Lambda Squared') // Hi, LAMBDA SQUARED!
  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('Lambda Squared') // Hi, LAMBDA SQUARED!
  127. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('Lambda Squared') // Hi, LAMBDA SQUARED!
  128. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('Lambda Squared') // Hi, LAMBDA SQUARED!
  129. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('Lambda Squared') // Hi, LAMBDA SQUARED!
  130. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, )(name) excitedGreeting('Lambda Squared') // Hi, LAMBDA SQUARED!
  131. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, // LAMBDA SQUARED greet('Hi'), exclaim, )(name) excitedGreeting('Lambda Squared') // Hi, LAMBDA SQUARED!
  132. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), // Hi, LAMBDA SQUARED exclaim, )(name) excitedGreeting('Lambda Squared') // Hi, LAMBDA SQUARED!
  133. const R = require('ramda') const excitedGreeting = name => R.pipe(

    toUpper, greet('Hi'), exclaim, // Hi, LAMBDA SQUARED! )(name) excitedGreeting('Lambda Squared') // Hi, LAMBDA SQUARED!
  134. Function Composition

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

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "Lambda Squared" -- Hi, LAMBDA SQUARED!
  136. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "Lambda Squared" -- Hi, LAMBDA SQUARED!
  137. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "Lambda Squared" -- Hi, LAMBDA SQUARED!
  138. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim excitedGreeting "Lambda Squared" -- Hi, LAMBDA SQUARED!
  139. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper -- LAMBDA SQUARED |> greet "Hi" |> exclaim excitedGreeting "Lambda Squared" -- Hi, LAMBDA SQUARED!
  140. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" -- Hi, LAMBDA SQUARED |> exclaim excitedGreeting "Lambda Squared" -- Hi, LAMBDA SQUARED!
  141. greet greeting name = greeting ++ ", " ++ name

    exclaim phrase = phrase ++ "!" excitedGreeting name = name |> String.toUpper |> greet "Hi" |> exclaim -- Hi, LAMBDA SQUARED! excitedGreeting "Lambda Squared" -- Hi, LAMBDA SQUARED!
  142. 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 } }
  143. 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 } }
  144. 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 } }
  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. Hard to test Hard to follow Code breaks unexpectedly Too

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

  151. Strong Static Types

  152. 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 }
  153. 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 }
  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 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 }
  156. -- TYPE MISMATCH -------------------------------------- App.elm The 2nd argument to function

    `createOrder` is causing a mismatch. 25| createOrder myShoppingCart "Tucker" ^^^^^^^^ Function `createOrder` is expecting the 2nd argument to be: { b | discounts : List Float , items : List { a | price : Float } , shippingCost : Float , tax : Float } But it is: String
  157. -- TYPE MISMATCH -------------------------------------- App.elm The 2nd argument to function

    `createOrder` is causing a mismatch. 25| createOrder myShoppingCart "Tucker" ^^^^^^^^ Function `createOrder` is expecting the 2nd argument to be: { b | discounts : List Float , items : List { a | price : Float } , shippingCost : Float , tax : Float } But it is: String
  158. -- TYPE MISMATCH -------------------------------------- App.elm The 2nd argument to function

    `createOrder` is causing a mismatch. 25| createOrder myShoppingCart "Tucker" ^^^^^^^^ Function `createOrder` is expecting the 2nd argument to be: { b | discounts : List Float , items : List { a | price : Float } , shippingCost : Float , tax : Float } But it is: String
  159. conference : String conference = "Lambda Squared" life : Int

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

    life = 42 greet : String -> String greet name = "Hello, " ++ name formatCurrency : Float -> String formatCurrency amount = "$" ++ toString amount
  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. Custom Types

  163. type alias Item = { price : Float }

  164. type alias Item = { price : Float }

  165. type alias Item = { price : Float }

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

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

    alias ShoppingCart = { items : List Item , discounts : List Float , tax : Float , shippingCost : Float }
  168. 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 }
  169. createOrder : ShoppingCart -> String -> Order createOrder shoppingCart customer

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

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

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

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

  174. type Status = New | Processing | Shipped

  175. type Status = New | Processing | Shipped

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

    Order = { status : Status , customer : String , total : Float }
  177. 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 }
  178. Hard to test Hard to follow Code breaks unexpectedly Too

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

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

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

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

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

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

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

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

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

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

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

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

  192. type Maybe a = Nothing | Just a

  193. type Maybe a = Nothing | Just a

  194. type Maybe a = Nothing | Just a

  195. type Maybe a = Nothing | Just a

  196. 42 Just 42 "Hi" Just "Hi"

  197. Just 42 [42] Nothing [ ]

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

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

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

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

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

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

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

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

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

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

    branches for all possibilities. 30|> case Dict.get id users of 31|> Just user -> 32|> user.name You need to account for the following values: Maybe.Nothing Add a branch to cover this pattern!
  208. getUserName : Id -> Dict Id User -> String getUserName

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

    && user.bestFriendId) { let friend = users.get(user.bestFriendId) if (friend) { return friend.location } } }
  210. 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
  211. getBestFriendLocation id users = Dict.get id users |> Maybe.andThen (\user

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    code Curried, Composable Functions Code breaks unexpectedly Immutable Data Hard to test Pure Functions Hard to follow Declarative Code
  226. 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 Hard to follow Declarative Code
  227. Thanks! bit.ly/lambda-squared-practical-fp Jeremy Fairbank @elpapapollo