Day in the Life of a Functional Programmer

Day in the Life of a Functional Programmer

Ff619670d30ebdeefd49cf10af8e3292?s=128

Richard Dallaway

February 23, 2017
Tweet

Transcript

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

    Richard Dallaway
  2. Modern Development Func onal Programming with Types

  3. Why now?

  4. Why now? Modern compilers have powerful type system

  5. A Day in the Life

  6. Func onal Programming Is For Everyone Makes Change Easier

  7. The Morning Standup

  8. CRM RDBMS REST Server

  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" } ]
  10. As a customer, I should have a subscrip on, so

    I can receive the company magazine.
  11. No subscrip on Ac ve subscrip on Lapsed subscrip on

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

    ideas
  13. sealed trait Subscription

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

    Active extends Subscription case object Lapsed extends Subscription
  15. sealed trait Subscription case object Never extends Subscription case class

    Active(expires: Date) extends Subscription case object Lapsed extends Subscription
  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 )
  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 = ???
  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) => ??? }
  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 }
  20. “Marke ng just reminded me we give free subscrip ons

    to some customers” — Product Owner
  21. sealed trait Subscription case object Never extends Subscription case class

    Active(expires: Date) extends Subscription case object Lapsed extends Subscription
  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
  23. Move side effects out

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

  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 ...
  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 }
  27. ADTs Safe Versi le Separate data & behaviour

  28. Output JSON to the Client { "id": 1, "name": "Alice",

    "phone": "+1 555 1234", "sub": { "type": "expired" } }
  29. Type Class Big things from ny building blocks

  30. Turn Data into JSON text 7.toString

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

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

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

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

    + "\"" "Hello" { greeting: "Hello", count: 7 } Int => String String => String T => String
  35. trait JsonFormat[T] { def format(value: T): String }

  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 + "\"" }
  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
  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
  39. [ "Hello", "there" ] [ 1, 2, 3, 4 ]

    [ [1,2], [3,4] ]
  40. implicit def listFormat[T] = new JsonFormat[List[T]] { def format(values: List[T]):

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

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

    String = { val formattedValues = for { v <- values } yield ??? "[" + formattedValues.mkString(",") + "]" } }
  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(",") + "]" } }
  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]
  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] ]
  46. Type Class Small Building Blocks Combined by the Compiler Used

    Everywhere
  47. case class Customer( name : String, phone : String, id

    : Long ) sql" select name, phone, id from customer ".query[Customer]
  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" })
  49. Used Everywhere ADTs Type Classes

  50. As a customer, I should have an control over my

    details, so I can update some or all of them.
  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() } } }
  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 } }
  53. Simplified Patch { "name" : "Robert" } or { "name"

    : "Robert", "phone : "+1 555 555" }
  54. Simplified Patch (Customer, Patch) => Customer

  55. case class Customer( id : Long, name : String, phone

    : String ) case class CustomerPatch( name : Option[String], phone : Option[String] )
  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) = ???
  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)
  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] = ???
  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 } }
  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")
  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] )
  62. ADTs Safe, Versi le Type Classes Combining Small Building Blocks

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

    ve, Scala.js
  64. Help Make Change Easier

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