Skip to content

Instantly share code, notes, and snippets.

@markus1189
Last active January 31, 2016 10:01
Show Gist options
  • Save markus1189/760d1078b462c282ab44 to your computer and use it in GitHub Desktop.
Save markus1189/760d1078b462c282ab44 to your computer and use it in GitHub Desktop.
Building houses in a type safe way
// source: https://gist.github.com/markus1189/760d1078b462c282ab44
/* Example of using phantom types to make a type safe builder.
* A House requires:
* - a roof which can be either flat or normal
* - a base, after one set this can never be removed again and neither added again
* - side walls, can be removed/added if (not) present
* - a door, can be removed/added if (not) present
*
* the `build` method can only be called if the builder is in a valid
* state and will create the correct result type, e.g. a flat house or
* a normal house. Note that these two classes do NOT share a common
* parent
*/
sealed abstract class Roof
sealed trait FlatRoof extends Roof
sealed trait NormalRoof extends Roof
sealed abstract class Bool
final abstract class True extends Bool
final abstract class False extends Bool
final case object FlatHouse
final case object NormalHouse
sealed trait ResultFor[R <: Roof] {
type S
def make: S
}
object ResultFor {
type Aux[R0<:Roof,S0] = ResultFor[R0] { type S = S0 }
implicit val resultForFlat: ResultFor.Aux[FlatRoof,FlatHouse.type] =
new ResultFor[FlatRoof] {
type S = FlatHouse.type
def make = FlatHouse
}
implicit val resultForNormal: ResultFor.Aux[NormalRoof,NormalHouse.type] =
new ResultFor[NormalRoof] {
type S = NormalHouse.type
def make = NormalHouse
}
}
sealed class HouseBuilder[R <: Roof, Base <: Bool, Sides <: Bool, Door <: Bool] private {
def removeRoof[S >: SomeRoof] = new HouseBuilder[Nothing,Base,Sides,Door]
def addFlatRoof(implicit ev: R =:= Nothing) =
new HouseBuilder[FlatRoof,Base,Sides,Door]
def addNormalRoof(implicit ev: R =:= Nothing) =
new HouseBuilder[NormalRoof,Base,Sides,Door]
def removeDoor(implicit ev: Door =:= True) =
new HouseBuilder[R,Base,Sides,False]
def addDoor(implicit ev: Door =:= False) =
new HouseBuilder[R,Base,Sides,True]
def removeSides(implicit ev: Sides =:= True) =
new HouseBuilder[R,Base,False,Door]
def addSides(implicit ev: Sides =:= False) =
new HouseBuilder[R,Base,True,Door]
def addBase(implicit ev: Base =:= False) =
new HouseBuilder[R,True,Sides,Door]
def build[A](implicit
evRoof: ResultFor.Aux[R,A],
evBase: Base =:= True,
evSides: Sides =:= True,
evDoor: Door =:= True): A = evRoof.make
}
object HouseBuilder {
def initial: HouseBuilder[Nothing,False,False,False] = new HouseBuilder
def from(house: FlatHouse.type): HouseBuilder[FlatRoof,True,True,True] = new HouseBuilder
def from(house: NormalHouse.type): HouseBuilder[NormalRoof,True,True,True] = new HouseBuilder
private final abstract class SomeRoof extends FlatRoof with NormalRoof
}
val normalHouse: NormalHouse.type = HouseBuilder.initial.addDoor.removeDoor.addSides.removeSides.addBase.addFlatRoof.removeRoof.addNormalRoof.addSides.addDoor.build
val flat: FlatHouse.type = HouseBuilder.from(normalHouse).removeRoof.addFlatRoof.build
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment