Skip to content

Instantly share code, notes, and snippets.

@MaximilianoFelice
Last active July 26, 2023 22:49
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save MaximilianoFelice/55ffa549172799fd359415a79a1f2d17 to your computer and use it in GitHub Desktop.
Save MaximilianoFelice/55ffa549172799fd359415a79a1f2d17 to your computer and use it in GitHub Desktop.
A Builder example in Scala using Phantom Types
case class Food(ingredients: Seq[String])
class Chef[Pizza <: Chef.Pizza] protected (ingredients: Seq[String]) {
import Chef.Pizza._
def addCheese(cheeseType: String): Chef[Pizza with Cheese] = Chef(ingredients :+ cheeseType)
def addTopping(toppingType: String): Chef[Pizza with Topping] = Chef(ingredients :+ toppingType)
def addDough: Chef[Pizza with Dough] = Chef(ingredients :+ "dough")
def build(implicit ev: Pizza =:= FullPizza): Food = Food(ingredients)
}
object Chef {
sealed trait Pizza
object Pizza {
sealed trait EmptyPizza extends Pizza
sealed trait Cheese extends Pizza
sealed trait Topping extends Pizza
sealed trait Dough extends Pizza
type FullPizza = EmptyPizza with Cheese with Topping with Dough
}
def apply[T <: Pizza](ingredients: Seq[String]): Chef[T] = new Chef[T](ingredients)
def apply(): Chef[Pizza.EmptyPizza] = apply[Pizza.EmptyPizza](Seq())
}
@lwilli
Copy link

lwilli commented Apr 28, 2020

Thanks for the great article on this pattern! It looks like this code is a bit different than what is in the article though. Particularly, the apply methods and the protected class constructor don't seem to be in the article. It might be worth mentioning these in the article since the builder is safer because it prevents someone from doing new Chef[Chef.Pizza.FullPizza]().build, which the original code from the article allows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment