Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Jeremy Fairbank

January 10, 2020
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. Available in
    print or e-book
    programming-elm.com

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

    View Slide

  11. Pure
    Total
    Idempotent
    Monad
    Functor

    View Slide

  12. View Slide

  13. 6
    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
    Pure

    View Slide

  36. View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  50. youtu.be/yTkzNHF6rMs

    View Slide

  51. Hard to test Hard to follow


    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  54. Imperative

    View Slide

  55. Declarative

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  62. Hard to test Hard to follow
    Code breaks
    unexpectedly


    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  74. Mutable Data

    View Slide

  75. Immutable Data
    Safety and consistency

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  80. Create New State

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  94. Hard to test Hard to follow
    Code breaks
    unexpectedly
    Too much code

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  111. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  120. const excitedGreeting = name =>
    // ...

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

  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!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  136. Function
    Composition

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  152. createOrder(myShoppingCart, 'Tucker')

    View Slide

  153. Strong Static
    Types

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  164. ?

    View Slide

  165. View Slide

  166. Custom
    Types

    View Slide

  167. type Item = {
    price: number,
    }

    View Slide

  168. type Item = {
    price: number,
    }

    View Slide

  169. type Item = {
    price: number,
    }

    View Slide

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

    View Slide

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

    View Slide

  172. type Item = {
    price: number,
    }
    type ShoppingCart = {
    items: Item[],
    discounts: number[],
    tax: number,
    shippingCost: number,
    }
    type Order = {
    status: string,
    customer: string,
    total: number,
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  180. enum Status {
    New,
    Processing,
    Shipped,
    }
    type Order = {
    status: Status,
    customer: string,
    total: number,
    }

    View Slide

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

    View Slide

  182. Hard to test Hard to follow
    Code breaks
    unexpectedly
    Too much code
    Scary to refactor
    Oh, null and
    undefined

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  192. 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

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

    View Slide

  194. View Slide

  195. if () {}
    if () {}
    if
    ()
    {}

    View Slide

  196. type Maybe a
    = Nothing
    | Just a

    View Slide

  197. type Maybe a
    = Nothing
    | Just a

    View Slide

  198. type Maybe a
    = Nothing
    | Just a

    View Slide

  199. type Maybe a
    = Nothing
    | Just a

    View Slide

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

    View Slide

  201. Just 42 [42]
    Nothing [ ]

    View Slide

  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

    View Slide

  203. function mapGet(key, map) {
    let result = map.get(key)
    return result == null
    ? Maybe.nothing()
    : Maybe.just(result)
    }

    View Slide

  204. let mapGet = Maybe.wrapReturn(
    (key, map) => map.get(key)
    )

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  210. function getUserName(
    id: Id,
    users: Map
    ): string {
    let user = mapGet(id, users)
    if (user.isJust()) {
    return user.unsafelyUnwrap().name
    }
    }

    View Slide

  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

    View Slide

  212. function getUserName(
    id: Id,
    users: Map
    ): string {
    let user = mapGet(id, users)
    return user.isJust()
    ? user.unsafelyUnwrap().name
    : ''
    }

    View Slide

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

    View Slide

  214. function getBestFriendLocation(
    id: Id,
    users: Map
    ): Maybe {
    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()
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  232. Hard to test Hard to follow
    Code breaks
    unexpectedly
    Too much code
    Scary to refactor
    Oh, null and
    undefined

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  239. Thanks!
    bit.ly/cm-practical-fp
    Jeremy Fairbank
    @elpapapollo

    View Slide