Share
Embed
Brighton Java: Day in the life...
Presentation to Brighton Java. https://www.meetup.com/Brighton-Java/events/239733337/
Recording: https://www.youtube.com/watch?v=S5BvWPIj-TY
Richard Dallaway
May 31, 2017
Transcript
63 slides
-
A Day in the Life of a Func onal Programmer
Richard Dallaway (@d6y) -
$ 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" } ] -
As a customer, I should have a subscrip on, so
I can receive the magazine. -
sealed trait Subscription case object Never extends Subscription case object
Active extends Subscription case object Lapsed extends Subscription -
sealed trait Subscription case object Never extends Subscription case class
Active(expires: Date) extends Subscription case object Lapsed extends Subscription -
sealed trait Subscription case object Never extends Subscription case class
Active(expires: Date) extends Subscription case object Lapsed extends Subscription case class Customer( id : Long, name : String, phone : String, sub : Subscription ) -
sealed trait Subscription case object Never extends Subscription case class
Active(expires: Date) extends Subscription case object Lapsed extends Subscription case class Customer( id : Long, name : String, phone : String, sub : Subscription ) def transition(sub: Subscription, at: Date): Subscription = ??? -
sealed trait Subscription case object Never extends Subscription case class
Active(expires: Date) extends Subscription case object Lapsed extends Subscription case class Customer( id : Long, name : String, phone : String, sub : Subscription ) def transition(sub: Subscription, at: Date): Subscription = sub match { case Lapsed | Never => sub case Active(exp) => ??? } -
sealed trait Subscription case object Never extends Subscription case class
Active(expires: Date) extends Subscription case object Lapsed extends Subscription case class Customer( id : Long, 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 } -
“Marke ng just reminded me we give free subscrip ons
to some customers” — Product Owner -
sealed trait Subscription case object Never extends Subscription case class
Active(expires: Date) extends Subscription case object Lapsed extends Subscription -
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 -
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 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 } -
Output JSON to the Client { "id": 1, "name": "Alice",
"phone": "+1 555 1234", "sub": { "type": "expired" } } -
Turn Data into JSON text 7.toString 7 "\"" + "Hello"
+ "\"" "Hello" -
Turn Data into JSON text 7.toString 7 "\"" + "Hello"
+ "\"" "Hello" { greeting: "Hello", count: 7 } -
Turn Data into JSON text 7.toString 7 "\"" + "Hello"
+ "\"" "Hello" { greeting: "Hello", count: 7 } Int => String String => String -
Turn Data into JSON text 7.toString 7 "\"" + "Hello"
+ "\"" "Hello" { greeting: "Hello", count: 7 } Int => String String => String T => String -
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 + "\"" } -
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 -
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 -
[ "Hello", "there" ] [ 1, 2, 3, 4 ]
[ [1,2], [3,4] ] -
implicit def listFormat[T] = new JsonFormat[List[T]] { def format(values: List[T]):
String = { val formattedValues = { ??? } "[" + formattedValues.mkString(",") + "]" } } -
implicit def listFormat[T] = new JsonFormat[List[T]] { def format(values: List[T]):
String = { val formattedValues = for { v <- values } yield ??? "[" + formattedValues.mkString(",") + "]" } } -
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(",") + "]" } } -
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] -
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] ] -
case class Customer( name : String, phone : String, id
: Long ) sql" select name, phone, id from customer ".query[Customer] -
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" }) -
As a customer, I want to update the details you
hold for me. -
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() } } } -
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 } } -
Simplified Patch { "name" : "Robert" } or { "name"
: "Robert", "phone : "+1 555 555" } -
case class Customer( id : Long, name : String, phone
: String ) case class CustomerPatch( name : Option[String], phone : Option[String] ) -
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) = ??? -
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) -
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] = ??? -
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 } } -
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") -
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] ) -
Thank You h ps:/ /underscore.io/ h ps:/ /underscore.io/books/essen al‐scala/ h
ps:/ /www.meetup.com/Func onal‐Brighton/