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

Day in the Life of a Functional Programmer

Day in the Life of a Functional Programmer

Richard Dallaway

February 23, 2017
Tweet

More Decks by Richard Dallaway

Other Decks in Technology

Transcript

  1. A Day in the Life of a Func onal
    Programmer
    Richard Dallaway

    View Slide

  2. Modern Development
    Func onal Programming with Types

    View Slide

  3. Why now?

    View Slide

  4. Why now?
    Modern compilers have powerful type
    system

    View Slide

  5. A Day in the Life

    View Slide

  6. Func onal Programming
    Is For Everyone
    Makes Change Easier

    View Slide

  7. The Morning Standup

    View Slide

  8. CRM
    RDBMS
    REST Server

    View Slide

  9. $ http GET :8080/customers
    HTTP/1.1 200 OK
    Content-Type: application/json
    [
    {
    "id": 1,
    "name": "Alice",
    "phone": "+1 555 1234"
    },
    {
    "id": 2,
    "name": "Bob",
    "phone": "+1 555 5678"
    }
    ]

    View Slide

  10. As a customer, I should have a
    subscrip on, so I can receive the
    company magazine.

    View Slide

  11. No subscrip on
    Ac ve subscrip on
    Lapsed subscrip on

    View Slide

  12. Algebraic Data Types
    Structural Recusion
    Unfamiliar names for simple powerful ideas

    View Slide

  13. sealed trait Subscription

    View Slide

  14. sealed trait Subscription
    case object Never extends Subscription
    case object Active extends Subscription
    case object Lapsed extends Subscription

    View Slide

  15. sealed trait Subscription
    case object Never extends Subscription
    case class Active(expires: Date) extends Subscription
    case object Lapsed extends Subscription

    View Slide

  16. sealed trait Subscription
    case object Never extends Subscription
    case class Active(expires: Date) extends Subscription
    case object Lapsed extends Subscription
    case class Customer(
    name : String,
    phone : String,
    sub : Subscription
    )

    View Slide

  17. sealed trait Subscription
    case object Never extends Subscription
    case class Active(expires: Date) extends Subscription
    case object Lapsed extends Subscription
    case class Customer(
    name : String,
    phone : String,
    sub : Subscription
    )
    def transition(sub: Subscription, at: Date): Subscription = ???

    View Slide

  18. sealed trait Subscription
    case object Never extends Subscription
    case class Active(expires: Date) extends Subscription
    case object Lapsed extends Subscription
    case class Customer(
    name : String,
    phone : String,
    sub : Subscription
    )
    def transition(sub: Subscription, at: Date): Subscription =
    sub match {
    case Lapsed | Never => sub
    case Active(exp) => ???
    }

    View Slide

  19. sealed trait Subscription
    case object Never extends Subscription
    case class Active(expires: Date) extends Subscription
    case object Lapsed extends Subscription
    case class Customer(
    name : String,
    phone : String,
    sub : Subscription
    )
    def transition(sub: Subscription, at: Date): Subscription =
    sub match {
    case Lapsed | Never => sub
    case Active(exp) =>
    if (at > exp) Lapsed else sub
    }

    View Slide

  20. “Marke ng just reminded me we
    give free subscrip ons to some
    customers”
    — Product Owner

    View Slide

  21. sealed trait Subscription
    case object Never extends Subscription
    case class Active(expires: Date) extends Subscription
    case object Lapsed extends Subscription

    View Slide

  22. sealed trait Subscription
    case object Never extends Subscription
    case class Active(expires: Date) extends Subscription
    case object Lapsed extends Subscription
    case object Freebie extends Subscription
    def transition(sub: Subscription, at: Date): Subscription =
    sub match {
    case Lapsed | Never => sub
    case Active(exp) =>
    if (at > exp) Lapsed else sub
    }
    ERROR: non‐exhaus ve match

    View Slide

  23. Move side effects out

    View Slide

  24. def transition(c: Customer, at: Date): Action = ???

    View Slide

  25. def transition(c: Customer, at: Date): Action = ???
    sealed trait Action
    case object NoAction extends Action
    case class EncourageRenewal(c: Customer) extends Action
    case class Expire(c: Customer) extends Action
    ...

    View Slide

  26. def transition(c: Customer, at: Date): Action = ???
    sealed trait Action
    case object NoAction extends Action
    case class EncourageRenewal(c: Customer) extends Action
    case class Expire(c: Customer) extends Action
    ...
    def perform(a: Action): Unit =
    a match {
    case NoAction =>
    case Expire(c) => sendSMS(c.phone)
    ...etc
    }

    View Slide

  27. ADTs
    Safe
    Versi le
    Separate data & behaviour

    View Slide

  28. Output JSON to the Client
    {
    "id": 1,
    "name": "Alice",
    "phone": "+1 555 1234",
    "sub": {
    "type": "expired"
    }
    }

    View Slide

  29. Type Class
    Big things from ny building blocks

    View Slide

  30. Turn Data into JSON text
    7.toString

    View Slide

  31. Turn Data into JSON text
    7.toString
    7
    "\"" + "Hello" + "\""
    "Hello"

    View Slide

  32. Turn Data into JSON text
    7.toString
    7
    "\"" + "Hello" + "\""
    "Hello"
    { greeting: "Hello", count: 7 }

    View Slide

  33. Turn Data into JSON text
    7.toString
    7
    "\"" + "Hello" + "\""
    "Hello"
    { greeting: "Hello", count: 7 }
    Int => String
    String => String

    View Slide

  34. Turn Data into JSON text
    7.toString
    7
    "\"" + "Hello" + "\""
    "Hello"
    { greeting: "Hello", count: 7 }
    Int => String
    String => String
    T => String

    View Slide

  35. trait JsonFormat[T] {
    def format(value: T): String
    }

    View Slide

  36. trait JsonFormat[T] {
    def format(value: T): String
    }
    val intFormat = new JsonFormat[Int] {
    def format(value: Int): String = value.toString
    }
    val strFormat = new JsonFormat[String] {
    def format(value: String): String =
    "\"" + value + "\""
    }

    View Slide

  37. trait JsonFormat[T] {
    def format(value: T): String
    }
    val intFormat = new JsonFormat[Int] {
    def format(value: Int): String = value.toString
    }
    val strFormat = new JsonFormat[String] {
    def format(value: String): String =
    "\"" + value + "\""
    }
    def outputJson[T](value: T)(jf: JsonFormat[T]) =
    jf.format(value)
    outputJson(7)(intFormat)
    // 7

    View Slide

  38. trait JsonFormat[T] {
    def format(value: T): String
    }
    implicit val intFormat = new JsonFormat[Int] {
    def format(value: Int): String = value.toString
    }
    implicit val strFormat = new JsonFormat[String] {
    def format(value: String): String =
    "\"" + value + "\""
    }
    def outputJson[T](value: T)(implicit jf: JsonFormat[T]) =
    jf.format(value)
    outputJson(7)
    // 7

    View Slide

  39. [ "Hello", "there" ]
    [ 1, 2, 3, 4 ]
    [ [1,2], [3,4] ]

    View Slide

  40. implicit def listFormat[T] =
    new JsonFormat[List[T]] {
    def format(values: List[T]): String = {
    ???
    }
    }

    View Slide

  41. implicit def listFormat[T] =
    new JsonFormat[List[T]] {
    def format(values: List[T]): String = {
    val formattedValues = {
    ???
    }
    "[" + formattedValues.mkString(",") + "]"
    }
    }

    View Slide

  42. implicit def listFormat[T] =
    new JsonFormat[List[T]] {
    def format(values: List[T]): String = {
    val formattedValues = for {
    v <- values
    } yield ???
    "[" + formattedValues.mkString(",") + "]"
    }
    }

    View Slide

  43. implicit def listFormat[T](implicit jf: JsonFormat[T]) =
    new JsonFormat[List[T]] {
    def format(values: List[T]): String = {
    val formattedValues = for {
    v <- values
    } yield jf.format(v)
    "[" + formattedValues.mkString(",") + "]"
    }
    }

    View Slide

  44. implicit def listFormat[T](implicit jf: JsonFormat[T]) =
    new JsonFormat[List[T]] {
    def format(values: List[T]): String = {
    val formattedValues = for {
    v <- values
    } yield jf.format(v)
    "[" + formattedValues.mkString(",") + "]"
    }
    }
    def outputJson[T](value: T)(implicit jf: JsonFormat[T]) =
    jf.format(value)
    outputJson( List(1,2,3,4) )
    [1,2,3,4]

    View Slide

  45. implicit def listFormat[T](implicit jf: JsonFormat[T]) =
    new JsonFormat[List[T]] {
    def format(values: List[T]): String = {
    val formattedValues = for {
    v <- values
    } yield jf.format(v)
    "[" + formattedValues.mkString(",") + "]"
    }
    }
    def outputJson[T](value: T)(implicit jf: JsonFormat[T]) =
    jf.format(value)
    outputJson( List(1,2,3,4) )
    [1,2,3,4]
    outputJson( List( List(1,2), List(3,4) ) )
    [ [1,2], [3,4] ]

    View Slide

  46. Type Class
    Small Building Blocks
    Combined by the Compiler
    Used Everywhere

    View Slide

  47. case class Customer(
    name : String,
    phone : String,
    id : Long
    )
    sql" select name, phone, id from customer ".query[Customer]

    View Slide

  48. case class Customer(
    name : String,
    phone : String,
    id : Long
    )
    sql" select name, phone, id from customer ".query[Customer]
    implicit val SubscriptionMeta: Meta[Subscription] =
    Meta[String].nxmap(ch =>
    ch match {
    case "A" => Active
    case "L" => Lapsed
    case "N" => Never
    }, sub =>
    sub match {
    case Active => "A"
    case Lapsed => "L"
    case Never => "N"
    })

    View Slide

  49. Used Everywhere
    ADTs
    Type Classes

    View Slide

  50. As a customer, I should have an
    control over my details, so I can
    update some or all of them.

    View Slide

  51. class CustomerAPI(db: Database) {
    val service = HttpService {
    case GET -> Root / "customers" =>
    Ok(db.list)
    case GET -> Root / "customers" / IntVar(id) =>
    db.find(id).flatMap {
    case Some(customer) => Ok(customer)
    case None => NotFound()
    }
    }
    }

    View Slide

  52. class CustomerAPI(db: Database) {
    val service = HttpService {
    case GET -> Root / "customers" =>
    Ok(db.list)
    case GET -> Root / "customers" / IntVar(id) =>
    db.find(id).flatMap {
    case Some(customer) => Ok(customer)
    case None => NotFound()
    }
    case PATCH -> Root / "customers" / IntVar(id) =>
    decode the patch
    lookup the customer in the database
    found a record?
    somehow apply the patch to the record
    update the database
    otherwise, 404 the request
    }
    }

    View Slide

  53. Simplified Patch
    {
    "name" : "Robert"
    }
    or
    {
    "name" : "Robert",
    "phone : "+1 555 555"
    }

    View Slide

  54. Simplified Patch
    (Customer, Patch) => Customer

    View Slide

  55. case class Customer(
    id : Long,
    name : String,
    phone : String
    )
    case class CustomerPatch(
    name : Option[String],
    phone : Option[String]
    )

    View Slide

  56. case class Customer(
    id : Long,
    name : String,
    phone : String
    )
    case class CustomerPatch(
    name : Option[String],
    phone : Option[String]
    )
    trait Merge[Into, From] {
    def merge(i: Into, f: From): Into
    }
    def merge[Into, From](i: Into, f: From) = ???

    View Slide

  57. case class Customer(
    id : Long,
    name : String,
    phone : String
    )
    case class CustomerPatch(
    name : Option[String],
    phone : Option[String]
    )
    trait Merge[Into, From] {
    def merge(i: Into, f: From): Into
    }
    def merge[Into, From](i: Into, f: From)
    (implicit m: Merge[Into, From]) = m.merge(i, f)

    View Slide

  58. case class Customer(
    id : Long,
    name : String,
    phone : String
    )
    case class CustomerPatch(
    name : Option[String],
    phone : Option[String]
    )
    trait Merge[Into, From] {
    def merge(i: Into, f: From): Into
    }
    def merge[Into, From](i: Into, f: From)
    (implicit m: Merge[Into, From]) = m.merge(i, f)
    implicit def customerMerge: Merge[Customer, CustomerPatch] =
    ???

    View Slide

  59. case class Customer(
    id : Long,
    name : String,
    phone : String
    )
    case class CustomerPatch(
    name : Option[String],
    phone : Option[String]
    )
    trait Merge[Into, From] {
    def merge(i: Into, f: From): Into
    }
    def merge[Into, From](i: Into, f: From)
    (implicit m: Merge[Into, From]) = m.merge(i, f)
    implicit def customerMerge: Merge[Customer, CustomerPatch] =
    ???
    implicit def optMerge = new Merge[String, Option[String]] {
    def merge(initial: String, update: Option[String]) = update match {
    case Some(value) => value
    case None => initial
    }
    }

    View Slide

  60. case class Customer(
    id : Long,
    name : String,
    phone : String
    )
    case class CustomerPatch(
    name : Option[String],
    phone : Option[String]
    )
    trait Merge[Into, From] {
    def merge(i: Into, f: From): Into
    }
    def merge[Into, From](i: Into, f: From)
    (implicit m: Merge[Into, From]) = m.merge(i, f)
    implicit def customerMerge: Merge[Customer, CustomerPatch] =
    ???
    merge(
    Customer(1, "Bob", "555-123"),
    CustomerPatch(Some("Robert"), None)
    )
    Customer(1, "Robert", "555-123")

    View Slide

  61. Pu ng it all together...
    case req @ PATCH -> Root / "customers" / IntVar(id) =>
    req.decode[CustomerPatch] { patch =>
    db.find(id).flatMap {
    case None => NotFound()
    case Some(customer) =>
    val updated = merge(customer, patch)
    Ok(db.update(updated))
    }
    }
    val merge = bulletin.AutoMerge[Customer, CustomerPatch]
    case class CustomerPatch(
    name : Option[String],
    phone : Option[String]
    )

    View Slide

  62. ADTs
    Safe, Versi le
    Type Classes
    Combining Small Building Blocks

    View Slide

  63. Scala
    Modern Powerful Compiler
    Func onal Programming
    JVM, Scala Na ve, Scala.js

    View Slide

  64. Help Make Change Easier

    View Slide

  65. Thank You
    h ps:/
    /underscore.io/

    View Slide