Last active
October 15, 2017 22:05
-
-
Save JamesMenetrey/d31028bfcb3d95980ced5dcdcdcc45ad to your computer and use it in GitHub Desktop.
Phantom types in Scala: leverage type safety over conditional branching
This file contains hidden or 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
| package scalaInAction.chapter8ScalableExtensibleComponents | |
| import org.scalatest.{FlatSpec, Matchers} | |
| class FoodReadyToBeEaten | |
| sealed trait Pizza | |
| object Pizza { | |
| sealed trait EmptyPizza extends Pizza | |
| sealed trait Cheese extends Pizza | |
| sealed trait Topping extends Pizza | |
| type FullPizza = EmptyPizza with Cheese with Topping | |
| } | |
| class Chef[CurrentPizza <: Pizza] { | |
| import Pizza._ | |
| def addCheese() = new Chef[CurrentPizza with Cheese] | |
| def addTopping() = new Chef[CurrentPizza with Topping] | |
| def build(implicit pizza: CurrentPizza =:= FullPizza) = new FoodReadyToBeEaten | |
| } | |
| /** | |
| * A phantom type is a manifestation of abstract type that has no effect on the runtime. These are useful to prove | |
| * static properties of the code using type evidences. As they have no effect on the runtime they can be erased | |
| * from the resulting code by the compiler once it has shown the constraints hold. | |
| * | |
| * For example, they can be useful for checking states. | |
| * | |
| * @see https://medium.com/@maximilianofelice/builder-pattern-in-scala-with-phantom-types-3e29a167e863 | |
| * | |
| * The operator <i>=:=</i> used in the code below is called <b>generalized type constraints</b> and allows the | |
| * developers to add constraints on a parametric type in a given function. Their purpose compared to type bound is | |
| * that they can be used only on one method/function. | |
| * | |
| * @see https://stackoverflow.com/q/3427345/2780334 | |
| */ | |
| class PhantomTypes extends FlatSpec with Matchers { | |
| import Pizza._ | |
| "Phantom types" can "be used as a builder pattern to guarantee the state and the right order of calls" in { | |
| val pizzaReadyToBeEaten = new Chef[EmptyPizza] | |
| .addCheese() | |
| .addTopping() | |
| .build | |
| pizzaReadyToBeEaten shouldBe a [FoodReadyToBeEaten] | |
| // The code below doesn't compile because build can be called without a FullPizza! | |
| "new Chef[EmptyPizza].addCheese.build" shouldNot compile | |
| "new Chef[EmptyPizza].addTopping.build" shouldNot compile | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment