Last active
January 31, 2016 10:01
-
-
Save markus1189/760d1078b462c282ab44 to your computer and use it in GitHub Desktop.
Building houses in a type safe way
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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