SlideShare a Scribd company logo
1 of 44
A typesafe builder
- a gentle introduction to type level programming -
Gregor Heine, Gilt Groupe
@greheine
- Disclaimer -
Joshua Bloch, Effective Java
"The builder pattern is a good choice when
designing classes whose constructors or static
factories would have more than a handful of
parameters."
What is a builder?
Factory for some (complex) target type
Fluent interface
One or more withXxx functions to add components
That return the builder type
Has a build method that instantiates and returns the target type
The Builder Pattern
Alternative to factory pattern
Useful when a lot of things need to be put together
Nicely readable code
Easily composable and updatable
Extendable
No constructor bloat
"Telescoping constructor anti-pattern"
Time for
a drink!
Time for a drink!
Choose a spirit
sealed abstract class Spirit _
case object Whisky extends Spirit
case object Gin extends Spirit _
Neat ot a mixer?
sealed abstract class Mixer _
case object Coke extends Mixer
case object Tonic extends Mixer
sealed abstract class Spirit
case object Whisky extends Spirit
case object Gin extends Spirit
sealed abstract class Mixer
case object Coke extends Mixer
case object Tonic extends Mixer
Time for a drink!
Choose a glass
sealed abstract class Glass
case object Short extends Glass
case object Tall extends Glass
case object Tulip extends Glass
Make it a double?
Place your order!
case class OrderOfDrink(glass: Glass,
spirit: Spirit,
mixer: Option[Mixer],
isDouble: Boolean)
sealed abstract class Glass
case object Short extends Glass
case object Tall extends Glass
case object Tulip extends Glass
case class OrderOfDrink(glass: Glass,
spirit: Spirit,
mixer: Option[Mixer],
isDouble: Boolean)
Java-style solution
class DrinkBuilder(glass: Option[Glass] = None,
spirit: Option[Spirit] = None,
mixer: Option[Mixer] = None,
isDouble: Boolean = false) {
def withGlass(g: Glass): DrinkBuilder = ???
def withSpirit(s: Spirit): DrinkBuilder = ???
def withMixer(m: Mixer): DrinkBuilder = ???
def asDouble(): DrinkBuilder = ???
def build(): OrderOfDrink = ???
}
case class DrinkBuilder(glass: Option[Glass] = None,
spirit: Option[Spirit] = None,
mixer: Option[Mixer] = None,
isDouble: Boolean = false) {
def withGlass(g: Glass): DrinkBuilder = ???
def withSpirit(s: Spirit): DrinkBuilder = ???
def withMixer(m: Mixer): DrinkBuilder = ???
def asDouble(): DrinkBuilder = ???
def build(): OrderOfDrink = ???
}
Java-style solution
class DrinkBuilder(glass: Option[Glass] = None,
spirit: Option[Spirit] = None,
mixer: Option[Mixer] = None,
isDouble: Boolean = false) {
def withGlass(g: Glass): DrinkBuilder = ???
def withSpirit(s: Spirit): DrinkBuilder = ???
def withMixer(m: Mixer): DrinkBuilder = ???
def asDouble(): DrinkBuilder = ???
def build(): OrderOfDrink = ???
}
Problems?
Required values
Input validation
case class DrinkBuilder(glass: Option[Glass] = None,
spirit: Option[Spirit] = None,
mixer: Option[Mixer] = None,
isDouble: Boolean = false) {
def withGlass(g: Glass): DrinkBuilder = ???
def withSpirit(s: Spirit): DrinkBuilder = ???
def withMixer(m: Mixer): DrinkBuilder = ???
def asDouble(): DrinkBuilder = ???
def build(): OrderOfDrink = ???
}
Solutions?
Build bad target
null values
invalid state
Throw Exception at runtime
Require non-optionals up-front
class DrinkBuilder(g: Glass, s: Spririt)
Validation function
def validate(): Boolean
Builder class hierarchy?
Wouldn't it be nice if the compiler could
tell me if the builder was complete?
Wouldn't it be nice if the compiler could
tell me if the builder was complete?
Enter: The Type Sytem!
A small adjustment
class DrinkBuilder(...) {
def withGlass(g: Glass): DrinkBuilder = ???
def withSpirit(s: Spirit): DrinkBuilder = ???
def withMixer(m: Mixer): DrinkBuilder = ???
def asDouble(): DrinkBuilder = ???
def build(): OrderOfDrink = ???
}
class DrinkBuilder(...) {
def withGlass(g: Glass): DrinkBuilder = ???
def withWhisky(): DrinkBuilder = ???
def withGin(): DrinkBuilder = ???
def withCoke(): DrinkBuilder = ???
def withTonic(): DrinkBuilder = ???
def asDouble(): DrinkBuilder = ???
def build(): OrderOfDrink = ???
}
Boolean Types
Phantom types
Never get instantiated
Used as type parameters
sealed trait TBool
sealed trait TTrue extends TBool
sealed trait TFalse extends TBool
Typesafe builder v0.1
class DrinkBuilder[GlassAdded <: TBool, SpiritAdded <: TBool] private(...) {
def withGlass[T >: GlassAdded <: TFalse](g: Glass) = {
new DrinkBuilder[TTrue, SpiritAdded](Some(g), spirit, mixer, isDouble)
}
def withWhisky[T >: SpiritAdded <: TFalse](): DrinkBuilder[GlassAdded, TTrue] = ???
def withGin[T >: SpiritAdded <: TFalse](): DrinkBuilder[GlassAdded, TTrue] = ???
def withCoke(): DrinkBuilder[GlassAdded, SpiritAdded] = ???
def withTonic(): DrinkBuilder[GlassAdded, SpiritAdded] = ???
def asDouble(): DrinkBuilder[GlassAdded, SpiritAdded] = ???
def build[T1 >: GlassAdded <: TTrue, T2 >: SpiritAdded <: TTrue](): OrderOfDrink = ???
}
object DrinkBuilder {
def apply() = new DrinkBuilder[TFalse, TFalse]()
}
Typesafe builder v0.1
Good:
Solves required parameters problem
Prevents supplying the same thing multiple times
Bad:
Doesn't solve "bad" drinks
Each additional constraint requires additional type param
Compiler error message hard to understand:
Error: inferred type arguments [TTrue,TFalse] do not conform to method build's type
parameter bounds [T1 >: TTrue <: TTrue,T2 >: TFalse <: TTrue]
DrinkBuilder().withGlass(Tall).build()
^
"Compiler error message hard to understand"
From Predef.scala:
@implicitNotFound(msg = "Cannot prove that ${From} =:= ${To}.")
sealed abstract class =:=[From, To] extends (From => To) with Serializable
private[this] final val singleton_=:= = new =:=[Any,Any] { def apply(x: Any): Any = x }
object =:= {
implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A]
}
// for summoning implicit values from the nether world
@inline def implicitly[T](implicit e: T) = e
HOWTO: Check type equality
scala> implicitly[=:=[String, String]]
res0: =:=[String,String] = <function1>
scala> implicitly[String =:= String]
res1: =:=[String,String] = <function1>
scala> implicitly[Int =:= AnyVal]
<console>:8: error: Cannot prove that Int =:= AnyVal.
implicitly[Int =:= AnyVal]
^
scala> implicitly[Int <:< AnyVal]
res1: <:<[Int,AnyVal] = <function1>
Typesafe builder v0.2
class DrinkBuilder[GlassAdded <: TBool, SpiritAdded <: TBool] private() {
def withGlass(g: Glass)
(implicit ev: GlassAdded =:= TFalse): DrinkBuilder[TTrue, SpiritAdded] =
???
def withWhisky()
(implicit ev: SpiritAdded =:= TFalse): DrinkBuilder[GlassAdded, TTrue] = ???
def withGin()
(implicit ev: SpiritAdded =:= TFalse): DrinkBuilder[GlassAdded, TTrue] = ???
def withCoke(): DrinkBuilder[GlassAdded, SpiritAdded] = ???
def withTonic(): DrinkBuilder[GlassAdded, SpiritAdded] = ???
def asDouble(): DrinkBuilder[GlassAdded, SpiritAdded] = ???
def build()(implicit ev1: GlassAdded =:= TTrue,
ev2: SpiritAdded =:= TTrue): OrderOfDrink = ???
}
Error: Cannot prove that TFalse =:= TTrue.
DrinkBuilder().withGlass(Tall).build()
^
"Additional constraints require additional type params"
sealed trait BuilderMethods {
type GlassAdded <: TBool
type SpititAdded <: TBool
}
Typesafe builder v0.3
sealed trait BuilderMethods {
type GlassAdded <: TBool
type SpititAdded <: TBool
}
class DrinkBuilder[M <: BuilderMethods] private (
def withGlass(g: Glass)(implicit ev: M#GlassAdded =:= TFalse) = {
new DrinkBuilder[M {type GlassAdded = TTrue}](Some(g), spirit, mixer, isDouble)
}
/* ... */
def build()(implicit ev1: GlassAdded =:= TTrue,
ev2: SpititAdded =:= TTrue): OrderOfDrink = ???
}
"Doesn't solve invalid states"
Let's go back to our boolean types, and add some boolean logic:
sealed trait TBool {
type If[T <: TBool, F <: TBool] <: TBool
}
sealed trait TTrue extends TBool {
type If[T <: TBool, F <: TBool] = T
}
sealed trait TFalse extends TBool {
type If[T <: TBool, F <: TBool] = F
}
"Doesn't solve invalid states"
Let's go back to our boolean types, and add some boolean logic:
sealed trait TBool {
type If[T <: TBool, F <: TBool] <: TBool
}
sealed trait TTrue extends TBool {
type If[T <: TBool, F <: TBool] = T
}
sealed trait TFalse extends TBool {
type If[T <: TBool, F <: TBool] = F
}
type && [A <: TBool, B <: TBool] = A#If[B, TFalse]
type || [A <: TBool, B <: TBool] = A#If[TTrue, B]
type Not[A <: TBool] = A#If[TFalse, TTrue]
First, let's test these boolean types
@inline def implicitly[T](implicit e: T) = e
test("compiles") {
implicitly[TTrue =:= TTrue]
implicitly[TFalse =:= TFalse]
implicitly[Not[TTrue] =:= TFalse]
implicitly[Not[TFalse] =:= TTrue]
implicitly[TFalse || TFalse =:= TFalse]
implicitly[TFalse || TTrue =:= TTrue]
implicitly[TTrue || TFalse =:= TTrue]
implicitly[TTrue || TTrue =:= TTrue]
implicitly[TFalse && TFalse =:= TFalse]
implicitly[TFalse && TTrue =:= TFalse]
implicitly[TTrue && TFalse =:= TFalse]
implicitly[TTrue && TTrue =:= TTrue]
implicitly[Not[TFalse && TTrue] =:= TTrue]
implicitly[TTrue && Not[TTrue] =:= TFalse]
}
First, let's test these boolean types
@inline def implicitly[T](implicit e: T) = e
test("doesn't compile, using scalatest") {
assertTypeError("implicitly[TTrue =:= TFalse]")
assertTypeError("implicitly[Not[TTrue] =:= TTrue]")
assertTypeError("implicitly[TFalse || TTrue =:= TFalse]")
assertTypeError("implicitly[TTrue && TFalse =:= TTrue]")
}
test("doesn't compile, using shapeless") {
illTyped("implicitly[TTrue =:= TFalse]")
illTyped("implicitly[Not[TTrue] =:= TTrue]")
illTyped("implicitly[TFalse || TTrue =:= TFalse]")
illTyped("implicitly[TTrue && TFalse =:= TTrue]")
}
Huzzah! Typesafe builder v1.0
sealed trait BuilderMethods {
type GlassAdded <: TBool
type WhiskyAdded <: TBool
type GinAdded <: TBool
type MixerAdded <: TBool
}
object DrinkBuilder {
def apply() = new DrinkBuilder[BuilderMethods { type GlassAdded = TFalse
type WhiskyAdded = TFalse
type GinAdded = TFalse
type MixerAdded = TFalse }
](None, None, None, false)
}
Huzzah! Typesafe builder v1.0
class DrinkBuilder[M <: BuilderMethods] private(/* ... */) {
def withGlass(g: Glass)(implicit ev: M#GlassAdded =:= TFalse) = {
new DrinkBuilder[M {type GlassAdded = TTrue}](Some(g), spirit, mixer, isDouble)
}
def withWhisky()(implicit ev: M#WhiskyAdded || M#GinAdded =:= TFalse) = ???
def withGin()(implicit ev: M#WhiskyAdded || M#GinAdded =:= TFalse) = ???
def withCoke()(implicit ev: M#WhiskyAdded && Not[M#MixerAdded] =:= TTrue) = ???
def withTonic()(implicit ev: M#GinAdded && Not[M#MixerAdded] =:= TTrue) = ???
def asDouble() = ???
def build()(implicit ev: M#GlassAdded && (M#WhiskyAdded || M#GinAdded) =:= TTrue) = ???
}
How do we test our builder?
test("can build a G & T") {
val drink = DrinkBuilder().withGin().withTonic().withGlass(Tulip).build()
drink.glass shouldBe Tulip
drink.spirit shouldBe Gin
drink.mixer shouldBe Some(Tonic)
drink.isDouble shouldBe false
}
How do we test our builder?
test("can build a G & T") {
val drink = DrinkBuilder().withGin().withTonic().withGlass(Tulip).build()
drink.glass shouldBe Tulip
drink.spirit shouldBe Gin
drink.mixer shouldBe Some(Tonic)
drink.isDouble shouldBe false
}
test("can't build from nothing") {
assertTypeError("DrinkBuilder().build()")
}
How do we test our builder?
test("can build a G & T") {
val drink = DrinkBuilder().withGin().withTonic().withGlass(Tulip).build()
drink.glass shouldBe Tulip
drink.spirit shouldBe Gin
drink.mixer shouldBe Some(Tonic)
drink.isDouble shouldBe false
}
test("can't build from nothing") {
assertTypeError("DrinkBuilder().build()")
}
test("can't add wrong mixer") {
assertTypeError("DrinkBuilder().withWhisky().withTonic()")
}
How do we test our builder?
test("can build a G & T") {
val drink = DrinkBuilder().withGin().withTonic().withGlass(Tulip).build()
drink.glass shouldBe Tulip
drink.spirit shouldBe Gin
drink.mixer shouldBe Some(Tonic)
drink.isDouble shouldBe false
}
test("can't build from nothing") {
assertTypeError("DrinkBuilder().build()")
}
test("can't add wrong mixer") {
assertTypeError("DrinkBuilder().withWhisky().withTonic()")
}
test("can't add mixer before spirit") {
assertTypeError("DrinkBuilder().withCoke().withWhisky()")
}
http://github.com/gilt/gfc-util
More fun with types: Peano Numbers
sealed trait Nat
sealed trait Zero extends Nat
sealed trait Succ[N <: Nat] extends Nat
type _0 = Zero
type _1 = Succ[Zero]
type _2 = Succ[_1]
type _3 = Succ[_2]
More fun with types: Peano Numbers
sealed trait Nat {
type Plus[That <: Nat] <: Nat
}
sealed trait Zero extends Nat {
type Plus[That <: Nat] = That
}
sealed trait Succ[N <: Nat] extends Nat {
type Plus[That <: Nat] = Succ[N#Plus[That]]
}
type +[A <: Nat, B <: Nat] = A#Plus[B]
More fun with types: Peano Numbers
sealed trait Nat {
type Plus[That <: Nat] <: Nat
}
sealed trait Zero extends Nat {
type Plus[That <: Nat] = That
}
sealed trait Succ[N <: Nat] extends Nat {
type Plus[That <: Nat] = Succ[N#Plus[That]]
}
type +[A <: Nat, B <: Nat] = A#Plus[B]
implicitly[_0 =:= _0]
implicitly[_0 + _1 =:= _1]
implicitly[_1 + _1 =:= _2]
implicitly[_1 + _2 =:= _3]
assertTypeError("implicitly[_1 + _3 =:= _3]")
More fun with types: HLists
sealed trait HList
sealed trait HNil extends HList
case class HCons[H, T <: HList](head: H, tail: T)
More fun with types: HLists
sealed trait HList
sealed trait HNil extends HList {
def ::[V](v: V) = HCons(v, this)
}
case class HCons[H, T <: HList](head: H, tail: T) extends HList {
def ::[V](v: V) = HCons(v, this)
}
val HNil = new HNil{}
More fun with types: HLists
sealed trait HList
sealed trait HNil extends HList {
def ::[V](v: V): HCons[V, HNil] = HCons(v, this)
}
case class HCons[H, T <: HList](head: H, tail: T) extends HList {
def ::[V](v: V): HCons[V, HCons[H, T]] = HCons(v, this)
}
val HNil = new HNil{}
More fun with types: HLists
sealed trait HList
sealed trait HNil extends HList {
def ::[V](v: V) = HCons(v, this)
}
case class HCons[H, T <: HList](head: H, tail: T) extends HList {
def ::[V](v: V) = HCons(v, this)
}
val HNil = new HNil{}
val list = 10 :: "hello" :: true :: HNil
val ten: Int = list.head
val hello: String = list.tail.head
More fun with types: HLists
sealed trait HList
sealed trait HNil extends HList {
def ::[V](v: V) = HCons(v, this)
}
case class HCons[H, T <: HList](head: H, tail: T) extends HList {
def ::[V](v: V) = HCons(v, this)
}
val HNil = new HNil{}
val list: HCons[Int, HCons[String, HCons[Boolean, HNil]]] = 10 :: "hello" :: true :: HNil
More fun with types: HLists
sealed trait HList
sealed trait HNil extends HList {
def ::[V](v: V) = HCons(v, this)
}
case class HCons[H, T <: HList](head: H, tail: T) extends HList {
def ::[V](v: V) = HCons(v, this)
}
val HNil = new HNil{}
type ::[H, T <: HList] = HCons[H, T]
val list: Int :: String :: Boolean :: HNil = 10 :: "hello" :: true :: HNil
Thank You!
Any questions?
References
● Apocalisp - Type-Level Programming in Scala
http://goo.gl/gEQre6
● Rafael rambling - Type-safe Builder Pattern in Scala
http://goo.gl/SlqgL
● Joe Barnes - Typelevel Programming 101: The Subspace of Scala
https://goo.gl/ucOI52 http://goo.gl/8PqSDy
● Jesper Nordenberg - HList in Scala

More Related Content

Recently uploaded

HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️anilsa9823
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceCALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceanilsa9823
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerThousandEyes
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxbodapatigopi8531
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️Delhi Call girls
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Steffen Staab
 

Recently uploaded (20)

Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceCALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 

Featured

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by HubspotMarius Sescu
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTExpeed Software
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsPixeldarts
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthThinkNow
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfmarketingartwork
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024Neil Kimberley
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)contently
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024Albert Qian
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsKurio // The Social Media Age(ncy)
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Search Engine Journal
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summarySpeakerHub
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project managementMindGenius
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...RachelPearson36
 

Featured (20)

2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot2024 State of Marketing Report – by Hubspot
2024 State of Marketing Report – by Hubspot
 
Everything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPTEverything You Need To Know About ChatGPT
Everything You Need To Know About ChatGPT
 
Product Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage EngineeringsProduct Design Trends in 2024 | Teenage Engineerings
Product Design Trends in 2024 | Teenage Engineerings
 
How Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental HealthHow Race, Age and Gender Shape Attitudes Towards Mental Health
How Race, Age and Gender Shape Attitudes Towards Mental Health
 
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdfAI Trends in Creative Operations 2024 by Artwork Flow.pdf
AI Trends in Creative Operations 2024 by Artwork Flow.pdf
 
Skeleton Culture Code
Skeleton Culture CodeSkeleton Culture Code
Skeleton Culture Code
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
 
How to have difficult conversations
How to have difficult conversations How to have difficult conversations
How to have difficult conversations
 
Introduction to Data Science
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data Science
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
 

The typesafe builder pattern

  • 1. A typesafe builder - a gentle introduction to type level programming - Gregor Heine, Gilt Groupe @greheine
  • 3. Joshua Bloch, Effective Java "The builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters."
  • 4. What is a builder? Factory for some (complex) target type Fluent interface One or more withXxx functions to add components That return the builder type Has a build method that instantiates and returns the target type
  • 5. The Builder Pattern Alternative to factory pattern Useful when a lot of things need to be put together Nicely readable code Easily composable and updatable Extendable No constructor bloat "Telescoping constructor anti-pattern"
  • 7. Time for a drink! Choose a spirit sealed abstract class Spirit _ case object Whisky extends Spirit case object Gin extends Spirit _ Neat ot a mixer? sealed abstract class Mixer _ case object Coke extends Mixer case object Tonic extends Mixer sealed abstract class Spirit case object Whisky extends Spirit case object Gin extends Spirit sealed abstract class Mixer case object Coke extends Mixer case object Tonic extends Mixer
  • 8. Time for a drink! Choose a glass sealed abstract class Glass case object Short extends Glass case object Tall extends Glass case object Tulip extends Glass Make it a double? Place your order! case class OrderOfDrink(glass: Glass, spirit: Spirit, mixer: Option[Mixer], isDouble: Boolean) sealed abstract class Glass case object Short extends Glass case object Tall extends Glass case object Tulip extends Glass case class OrderOfDrink(glass: Glass, spirit: Spirit, mixer: Option[Mixer], isDouble: Boolean)
  • 9. Java-style solution class DrinkBuilder(glass: Option[Glass] = None, spirit: Option[Spirit] = None, mixer: Option[Mixer] = None, isDouble: Boolean = false) { def withGlass(g: Glass): DrinkBuilder = ??? def withSpirit(s: Spirit): DrinkBuilder = ??? def withMixer(m: Mixer): DrinkBuilder = ??? def asDouble(): DrinkBuilder = ??? def build(): OrderOfDrink = ??? } case class DrinkBuilder(glass: Option[Glass] = None, spirit: Option[Spirit] = None, mixer: Option[Mixer] = None, isDouble: Boolean = false) { def withGlass(g: Glass): DrinkBuilder = ??? def withSpirit(s: Spirit): DrinkBuilder = ??? def withMixer(m: Mixer): DrinkBuilder = ??? def asDouble(): DrinkBuilder = ??? def build(): OrderOfDrink = ??? }
  • 10. Java-style solution class DrinkBuilder(glass: Option[Glass] = None, spirit: Option[Spirit] = None, mixer: Option[Mixer] = None, isDouble: Boolean = false) { def withGlass(g: Glass): DrinkBuilder = ??? def withSpirit(s: Spirit): DrinkBuilder = ??? def withMixer(m: Mixer): DrinkBuilder = ??? def asDouble(): DrinkBuilder = ??? def build(): OrderOfDrink = ??? } Problems? Required values Input validation case class DrinkBuilder(glass: Option[Glass] = None, spirit: Option[Spirit] = None, mixer: Option[Mixer] = None, isDouble: Boolean = false) { def withGlass(g: Glass): DrinkBuilder = ??? def withSpirit(s: Spirit): DrinkBuilder = ??? def withMixer(m: Mixer): DrinkBuilder = ??? def asDouble(): DrinkBuilder = ??? def build(): OrderOfDrink = ??? }
  • 11. Solutions? Build bad target null values invalid state Throw Exception at runtime Require non-optionals up-front class DrinkBuilder(g: Glass, s: Spririt) Validation function def validate(): Boolean Builder class hierarchy?
  • 12. Wouldn't it be nice if the compiler could tell me if the builder was complete?
  • 13. Wouldn't it be nice if the compiler could tell me if the builder was complete? Enter: The Type Sytem!
  • 14. A small adjustment class DrinkBuilder(...) { def withGlass(g: Glass): DrinkBuilder = ??? def withSpirit(s: Spirit): DrinkBuilder = ??? def withMixer(m: Mixer): DrinkBuilder = ??? def asDouble(): DrinkBuilder = ??? def build(): OrderOfDrink = ??? } class DrinkBuilder(...) { def withGlass(g: Glass): DrinkBuilder = ??? def withWhisky(): DrinkBuilder = ??? def withGin(): DrinkBuilder = ??? def withCoke(): DrinkBuilder = ??? def withTonic(): DrinkBuilder = ??? def asDouble(): DrinkBuilder = ??? def build(): OrderOfDrink = ??? }
  • 15. Boolean Types Phantom types Never get instantiated Used as type parameters sealed trait TBool sealed trait TTrue extends TBool sealed trait TFalse extends TBool
  • 16. Typesafe builder v0.1 class DrinkBuilder[GlassAdded <: TBool, SpiritAdded <: TBool] private(...) { def withGlass[T >: GlassAdded <: TFalse](g: Glass) = { new DrinkBuilder[TTrue, SpiritAdded](Some(g), spirit, mixer, isDouble) } def withWhisky[T >: SpiritAdded <: TFalse](): DrinkBuilder[GlassAdded, TTrue] = ??? def withGin[T >: SpiritAdded <: TFalse](): DrinkBuilder[GlassAdded, TTrue] = ??? def withCoke(): DrinkBuilder[GlassAdded, SpiritAdded] = ??? def withTonic(): DrinkBuilder[GlassAdded, SpiritAdded] = ??? def asDouble(): DrinkBuilder[GlassAdded, SpiritAdded] = ??? def build[T1 >: GlassAdded <: TTrue, T2 >: SpiritAdded <: TTrue](): OrderOfDrink = ??? } object DrinkBuilder { def apply() = new DrinkBuilder[TFalse, TFalse]() }
  • 17. Typesafe builder v0.1 Good: Solves required parameters problem Prevents supplying the same thing multiple times Bad: Doesn't solve "bad" drinks Each additional constraint requires additional type param Compiler error message hard to understand: Error: inferred type arguments [TTrue,TFalse] do not conform to method build's type parameter bounds [T1 >: TTrue <: TTrue,T2 >: TFalse <: TTrue] DrinkBuilder().withGlass(Tall).build() ^
  • 18. "Compiler error message hard to understand" From Predef.scala: @implicitNotFound(msg = "Cannot prove that ${From} =:= ${To}.") sealed abstract class =:=[From, To] extends (From => To) with Serializable private[this] final val singleton_=:= = new =:=[Any,Any] { def apply(x: Any): Any = x } object =:= { implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A] } // for summoning implicit values from the nether world @inline def implicitly[T](implicit e: T) = e
  • 19. HOWTO: Check type equality scala> implicitly[=:=[String, String]] res0: =:=[String,String] = <function1> scala> implicitly[String =:= String] res1: =:=[String,String] = <function1> scala> implicitly[Int =:= AnyVal] <console>:8: error: Cannot prove that Int =:= AnyVal. implicitly[Int =:= AnyVal] ^ scala> implicitly[Int <:< AnyVal] res1: <:<[Int,AnyVal] = <function1>
  • 20. Typesafe builder v0.2 class DrinkBuilder[GlassAdded <: TBool, SpiritAdded <: TBool] private() { def withGlass(g: Glass) (implicit ev: GlassAdded =:= TFalse): DrinkBuilder[TTrue, SpiritAdded] = ??? def withWhisky() (implicit ev: SpiritAdded =:= TFalse): DrinkBuilder[GlassAdded, TTrue] = ??? def withGin() (implicit ev: SpiritAdded =:= TFalse): DrinkBuilder[GlassAdded, TTrue] = ??? def withCoke(): DrinkBuilder[GlassAdded, SpiritAdded] = ??? def withTonic(): DrinkBuilder[GlassAdded, SpiritAdded] = ??? def asDouble(): DrinkBuilder[GlassAdded, SpiritAdded] = ??? def build()(implicit ev1: GlassAdded =:= TTrue, ev2: SpiritAdded =:= TTrue): OrderOfDrink = ??? } Error: Cannot prove that TFalse =:= TTrue. DrinkBuilder().withGlass(Tall).build() ^
  • 21. "Additional constraints require additional type params" sealed trait BuilderMethods { type GlassAdded <: TBool type SpititAdded <: TBool }
  • 22. Typesafe builder v0.3 sealed trait BuilderMethods { type GlassAdded <: TBool type SpititAdded <: TBool } class DrinkBuilder[M <: BuilderMethods] private ( def withGlass(g: Glass)(implicit ev: M#GlassAdded =:= TFalse) = { new DrinkBuilder[M {type GlassAdded = TTrue}](Some(g), spirit, mixer, isDouble) } /* ... */ def build()(implicit ev1: GlassAdded =:= TTrue, ev2: SpititAdded =:= TTrue): OrderOfDrink = ??? }
  • 23. "Doesn't solve invalid states" Let's go back to our boolean types, and add some boolean logic: sealed trait TBool { type If[T <: TBool, F <: TBool] <: TBool } sealed trait TTrue extends TBool { type If[T <: TBool, F <: TBool] = T } sealed trait TFalse extends TBool { type If[T <: TBool, F <: TBool] = F }
  • 24. "Doesn't solve invalid states" Let's go back to our boolean types, and add some boolean logic: sealed trait TBool { type If[T <: TBool, F <: TBool] <: TBool } sealed trait TTrue extends TBool { type If[T <: TBool, F <: TBool] = T } sealed trait TFalse extends TBool { type If[T <: TBool, F <: TBool] = F } type && [A <: TBool, B <: TBool] = A#If[B, TFalse] type || [A <: TBool, B <: TBool] = A#If[TTrue, B] type Not[A <: TBool] = A#If[TFalse, TTrue]
  • 25. First, let's test these boolean types @inline def implicitly[T](implicit e: T) = e test("compiles") { implicitly[TTrue =:= TTrue] implicitly[TFalse =:= TFalse] implicitly[Not[TTrue] =:= TFalse] implicitly[Not[TFalse] =:= TTrue] implicitly[TFalse || TFalse =:= TFalse] implicitly[TFalse || TTrue =:= TTrue] implicitly[TTrue || TFalse =:= TTrue] implicitly[TTrue || TTrue =:= TTrue] implicitly[TFalse && TFalse =:= TFalse] implicitly[TFalse && TTrue =:= TFalse] implicitly[TTrue && TFalse =:= TFalse] implicitly[TTrue && TTrue =:= TTrue] implicitly[Not[TFalse && TTrue] =:= TTrue] implicitly[TTrue && Not[TTrue] =:= TFalse] }
  • 26. First, let's test these boolean types @inline def implicitly[T](implicit e: T) = e test("doesn't compile, using scalatest") { assertTypeError("implicitly[TTrue =:= TFalse]") assertTypeError("implicitly[Not[TTrue] =:= TTrue]") assertTypeError("implicitly[TFalse || TTrue =:= TFalse]") assertTypeError("implicitly[TTrue && TFalse =:= TTrue]") } test("doesn't compile, using shapeless") { illTyped("implicitly[TTrue =:= TFalse]") illTyped("implicitly[Not[TTrue] =:= TTrue]") illTyped("implicitly[TFalse || TTrue =:= TFalse]") illTyped("implicitly[TTrue && TFalse =:= TTrue]") }
  • 27. Huzzah! Typesafe builder v1.0 sealed trait BuilderMethods { type GlassAdded <: TBool type WhiskyAdded <: TBool type GinAdded <: TBool type MixerAdded <: TBool } object DrinkBuilder { def apply() = new DrinkBuilder[BuilderMethods { type GlassAdded = TFalse type WhiskyAdded = TFalse type GinAdded = TFalse type MixerAdded = TFalse } ](None, None, None, false) }
  • 28. Huzzah! Typesafe builder v1.0 class DrinkBuilder[M <: BuilderMethods] private(/* ... */) { def withGlass(g: Glass)(implicit ev: M#GlassAdded =:= TFalse) = { new DrinkBuilder[M {type GlassAdded = TTrue}](Some(g), spirit, mixer, isDouble) } def withWhisky()(implicit ev: M#WhiskyAdded || M#GinAdded =:= TFalse) = ??? def withGin()(implicit ev: M#WhiskyAdded || M#GinAdded =:= TFalse) = ??? def withCoke()(implicit ev: M#WhiskyAdded && Not[M#MixerAdded] =:= TTrue) = ??? def withTonic()(implicit ev: M#GinAdded && Not[M#MixerAdded] =:= TTrue) = ??? def asDouble() = ??? def build()(implicit ev: M#GlassAdded && (M#WhiskyAdded || M#GinAdded) =:= TTrue) = ??? }
  • 29. How do we test our builder? test("can build a G & T") { val drink = DrinkBuilder().withGin().withTonic().withGlass(Tulip).build() drink.glass shouldBe Tulip drink.spirit shouldBe Gin drink.mixer shouldBe Some(Tonic) drink.isDouble shouldBe false }
  • 30. How do we test our builder? test("can build a G & T") { val drink = DrinkBuilder().withGin().withTonic().withGlass(Tulip).build() drink.glass shouldBe Tulip drink.spirit shouldBe Gin drink.mixer shouldBe Some(Tonic) drink.isDouble shouldBe false } test("can't build from nothing") { assertTypeError("DrinkBuilder().build()") }
  • 31. How do we test our builder? test("can build a G & T") { val drink = DrinkBuilder().withGin().withTonic().withGlass(Tulip).build() drink.glass shouldBe Tulip drink.spirit shouldBe Gin drink.mixer shouldBe Some(Tonic) drink.isDouble shouldBe false } test("can't build from nothing") { assertTypeError("DrinkBuilder().build()") } test("can't add wrong mixer") { assertTypeError("DrinkBuilder().withWhisky().withTonic()") }
  • 32. How do we test our builder? test("can build a G & T") { val drink = DrinkBuilder().withGin().withTonic().withGlass(Tulip).build() drink.glass shouldBe Tulip drink.spirit shouldBe Gin drink.mixer shouldBe Some(Tonic) drink.isDouble shouldBe false } test("can't build from nothing") { assertTypeError("DrinkBuilder().build()") } test("can't add wrong mixer") { assertTypeError("DrinkBuilder().withWhisky().withTonic()") } test("can't add mixer before spirit") { assertTypeError("DrinkBuilder().withCoke().withWhisky()") }
  • 34. More fun with types: Peano Numbers sealed trait Nat sealed trait Zero extends Nat sealed trait Succ[N <: Nat] extends Nat type _0 = Zero type _1 = Succ[Zero] type _2 = Succ[_1] type _3 = Succ[_2]
  • 35. More fun with types: Peano Numbers sealed trait Nat { type Plus[That <: Nat] <: Nat } sealed trait Zero extends Nat { type Plus[That <: Nat] = That } sealed trait Succ[N <: Nat] extends Nat { type Plus[That <: Nat] = Succ[N#Plus[That]] } type +[A <: Nat, B <: Nat] = A#Plus[B]
  • 36. More fun with types: Peano Numbers sealed trait Nat { type Plus[That <: Nat] <: Nat } sealed trait Zero extends Nat { type Plus[That <: Nat] = That } sealed trait Succ[N <: Nat] extends Nat { type Plus[That <: Nat] = Succ[N#Plus[That]] } type +[A <: Nat, B <: Nat] = A#Plus[B] implicitly[_0 =:= _0] implicitly[_0 + _1 =:= _1] implicitly[_1 + _1 =:= _2] implicitly[_1 + _2 =:= _3] assertTypeError("implicitly[_1 + _3 =:= _3]")
  • 37. More fun with types: HLists sealed trait HList sealed trait HNil extends HList case class HCons[H, T <: HList](head: H, tail: T)
  • 38. More fun with types: HLists sealed trait HList sealed trait HNil extends HList { def ::[V](v: V) = HCons(v, this) } case class HCons[H, T <: HList](head: H, tail: T) extends HList { def ::[V](v: V) = HCons(v, this) } val HNil = new HNil{}
  • 39. More fun with types: HLists sealed trait HList sealed trait HNil extends HList { def ::[V](v: V): HCons[V, HNil] = HCons(v, this) } case class HCons[H, T <: HList](head: H, tail: T) extends HList { def ::[V](v: V): HCons[V, HCons[H, T]] = HCons(v, this) } val HNil = new HNil{}
  • 40. More fun with types: HLists sealed trait HList sealed trait HNil extends HList { def ::[V](v: V) = HCons(v, this) } case class HCons[H, T <: HList](head: H, tail: T) extends HList { def ::[V](v: V) = HCons(v, this) } val HNil = new HNil{} val list = 10 :: "hello" :: true :: HNil val ten: Int = list.head val hello: String = list.tail.head
  • 41. More fun with types: HLists sealed trait HList sealed trait HNil extends HList { def ::[V](v: V) = HCons(v, this) } case class HCons[H, T <: HList](head: H, tail: T) extends HList { def ::[V](v: V) = HCons(v, this) } val HNil = new HNil{} val list: HCons[Int, HCons[String, HCons[Boolean, HNil]]] = 10 :: "hello" :: true :: HNil
  • 42. More fun with types: HLists sealed trait HList sealed trait HNil extends HList { def ::[V](v: V) = HCons(v, this) } case class HCons[H, T <: HList](head: H, tail: T) extends HList { def ::[V](v: V) = HCons(v, this) } val HNil = new HNil{} type ::[H, T <: HList] = HCons[H, T] val list: Int :: String :: Boolean :: HNil = 10 :: "hello" :: true :: HNil
  • 44. References ● Apocalisp - Type-Level Programming in Scala http://goo.gl/gEQre6 ● Rafael rambling - Type-safe Builder Pattern in Scala http://goo.gl/SlqgL ● Joe Barnes - Typelevel Programming 101: The Subspace of Scala https://goo.gl/ucOI52 http://goo.gl/8PqSDy ● Jesper Nordenberg - HList in Scala

Editor's Notes

  1. Separate the construction of a complex object from its representation so that the same construction process can create different representations.
  2. We'll use these boolean types to encode type constraints on our builder
  3. Class that doesn't typecheck unless both of its type parameters are the same.
  4. Replace type bounds with an implicit parameter "Evidence" This makes the compiler look for an implicit value of type =:= parameterized with the proper types. Predef defines an implicit method which provides such instances if the left and right hand sides are of the exact same type
  5. Bundle all constraints in a single type
  6. Bundle all constraints in a single type
  7. Church boolean types, named after Alonzo Church, who first encoded data in the lambda calculus this way.
  8. Church boolean types, named after Alonzo Church, who first encoded data in the lambda calculus this way.
  9. Peano numbers are a simple way of representing the natural numbers using only a zero value and a successor function Does recursion at the type-level
  10. recursion at the type-level
  11. recursion at the type-level
  12. Why is :: not in the base trait? - Different type!
  13. What type is "val list"
  14. Ugly! Let's add a cons *type* (different to the cons *function*)
  15. Could go on, concatinating HLists, etc. but need to END!!!