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

    I can receive the company magazine.
  3. sealed trait Subscription case object Never extends Subscription case object

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

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

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

    Active(expires: Date) extends Subscription case object Lapsed extends Subscription
  11. 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
  12. 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 ...
  13. 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 }
  14. Output JSON to the Client { "id": 1, "name": "Alice",

    "phone": "+1 555 1234", "sub": { "type": "expired" } }
  15. Turn Data into JSON text 7.toString 7 "\"" + "Hello"

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

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

    + "\"" "Hello" { greeting: "Hello", count: 7 } Int => String String => String T => String
  18. 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 + "\"" }
  19. 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
  20. 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
  21. implicit def listFormat[T] = new JsonFormat[List[T]] { def format(values: List[T]):

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

    String = { val formattedValues = for { v <- values } yield ??? "[" + formattedValues.mkString(",") + "]" } }
  23. 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(",") + "]" } }
  24. 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]
  25. 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] ]
  26. case class Customer( name : String, phone : String, id

    : Long ) sql" select name, phone, id from customer ".query[Customer]
  27. 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" })
  28. As a customer, I should have an control over my

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

    : "Robert", "phone : "+1 555 555" }
  32. case class Customer( id : Long, name : String, phone

    : String ) case class CustomerPatch( name : Option[String], phone : Option[String] )
  33. 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) = ???
  34. 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)
  35. 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] = ???
  36. 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 } }
  37. 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")
  38. 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] )