$30 off During Our Annual Pro Sale. View Details »

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.

Jeremy Fairbank

October 22, 2018
Tweet

More Decks by Jeremy Fairbank

Other Decks in Programming

Transcript

  1. Practical
    Functional
    Programming
    Jeremy Fairbank
    @elpapapollo

    View Slide

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

    View Slide

  3. bit.ly/programming-elm

    View Slide

  4. Functional
    programming:

    View Slide

  5. Pure
    Functional
    programming:

    View Slide

  6. Pure
    Total
    Functional
    programming:

    View Slide

  7. Pure
    Total
    Idempotent
    Functional
    programming:

    View Slide

  8. Pure
    Total
    Idempotent
    Monad
    Functional
    programming:

    View Slide

  9. Pure
    Total
    Idempotent
    Monad
    Functor
    Functional
    programming:

    View Slide

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

    View Slide

  11. Pure
    Total
    Idempotent
    Monad
    Functor

    View Slide

  12. View Slide

  13. 5
    Become a rockstar
    programmer with these
    tricks!

    View Slide

  14. Hard to test



    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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'))
    })
    })

    View Slide

  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'))
    })
    })

    View Slide

  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'))
    })
    })

    View Slide

  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'))
    })
    })

    View Slide

  28. Side Effects

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. Less Predictable

    View Slide

  33. Harder to Test

    View Slide

  34. Functions

    View Slide

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

    View Slide

  36. View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  46. it('uses the correct url', () => {
    expect(url([1, 2, 3])).toEqual('/orders?ids=1,2,3')
    })

    View Slide

  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',
    ])
    })

    View Slide

  48. async function printOrderDetails(ids) {
    let orders = await fetchJson(url(ids))
    let details = detailsForOrders(orders)
    details.forEach(orderDetails => {
    console.log(orderDetails)
    })
    }

    View Slide

  49. Hard to test
    Code breaks
    unexpectedly



    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  58. let hobbies = this.hobbies.splice(0, 2).join(' and ')

    View Slide

  59. Mutable Data

    View Slide

  60. Immutable Data
    Safety and consistency

    View Slide

  61. class Person {
    constructor(name, hobbies) {
    this.name = name
    this.hobbies = Object.freeze(hobbies)
    }
    // ...
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  65. Create New State

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  79. Hard to test
    Code breaks
    unexpectedly
    Too much code


    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  90. const greet = greeting => name => `${greeting}, ${name}`
    const greetInEnglish = greet('Hi')
    const greetInSpanish = greet('Hola')
    Curried Function
    Partial Application

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  96. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  105. const excitedGreeting = name =>
    // ...

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  121. Function
    Composition

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  134. Hard to test
    Code breaks
    unexpectedly
    Too much code Scary to refactor

    View Slide

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

    View Slide

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

    View Slide

  137. createOrder(myShoppingCart, 'Tucker')

    View Slide

  138. Strong Static
    Types

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  149. Custom
    Types

    View Slide

  150. type alias Item =
    { price : Float }

    View Slide

  151. type alias Item =
    { price : Float }

    View Slide

  152. type alias Item =
    { price : Float }

    View Slide

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

    View Slide

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

    View Slide

  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
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  160. type Status
    = New
    | Processing
    | Shipped

    View Slide

  161. type Status
    = New
    | Processing
    | Shipped

    View Slide

  162. type Status
    = New
    | Processing
    | Shipped

    View Slide

  163. type Status
    = New
    | Processing
    | Shipped
    type alias Order =
    { status : Status
    , customer : String
    , total : Float
    }

    View Slide

  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
    }

    View Slide

  165. Hard to test
    Code breaks
    unexpectedly
    Too much code Scary to refactor
    Oh, null

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  170. getBestFriendLocation(42, users)
    Uncaught TypeError:
    Cannot read property
    'bestFriendId' of null

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  177. View Slide

  178. if () {}
    if () {}
    if
    ()
    {}

    View Slide

  179. type Maybe a
    = Nothing
    | Just a

    View Slide

  180. type Maybe a
    = Nothing
    | Just a

    View Slide

  181. type Maybe a
    = Nothing
    | Just a

    View Slide

  182. type Maybe a
    = Nothing
    | Just a

    View Slide

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

    View Slide

  184. Just 42 [42]
    Nothing [ ]

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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!

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  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)

    View Slide

  206. P.S.
    You kinda, sorta just learned monads

    View Slide

  207. Hard to test
    Code breaks
    unexpectedly
    Too much code Scary to refactor
    Oh, null

    View Slide

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

    View Slide

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

    View Slide

  210. Too much code
    Curried, Composable
    Functions
    Scary to refactor
    Oh, null
    Code breaks unexpectedly
    Immutable Data
    Hard to test
    Pure Functions

    View Slide

  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

    View Slide

  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

    View Slide

  213. Thanks!
    bit.ly/ato-practical-fp
    Jeremy Fairbank
    @elpapapollo

    View Slide