Skip to content

Instantly share code, notes, and snippets.

@ruescasd
Created November 25, 2015 22:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ruescasd/9e0fa196ab58f1fcb064 to your computer and use it in GitHub Desktop.
Save ruescasd/9e0fa196ab58f1fcb064 to your computer and use it in GitHub Desktop.
/*
* A typed, purely functional, sequential state machine modeling a cake
*
* The cake has these states
*
* Started => Poured => Mixed => Baked
*
* Shows how to deal with state functionally and how impossible operations
* are compile time errors rather than runtime errors.
*
* For example, it is a compile time error to try to bake an empty cake, or to try
* to eat a an unbaked cake. The cake's state cannot be inconsistent, its logic is encoded in
* method signatures.
*
**/
import com.github.nscala_time.time.Imports._
sealed class CakeState
case class Started(val date: DateTime = DateTime.now) extends CakeState
case class Poured(val eggs: Int, val flour: Int, val sugar: Int, val butter: Int, started: Started) extends CakeState
case class Mixed(val mixingTime: Int, poured: Poured) extends CakeState
case class Baked(val bakingTime: Int, mixed: Mixed) extends CakeState
class Cake[S <: CakeState] private (val state: S) {
override def toString() = state.toString
}
object Cake {
def start() = {
println("Going to start a new Cake!")
new Cake(Started())
}
def pour(in: Cake[Started], eggs: Int, flour: Int, sugar: Int, butter: Int) = {
println("Pouring the ingredients...")
if(eggs <= 0 || flour <= 0 || sugar <= 0 || butter <= 0) {
Left("You gave me bad ingredients!")
}
else {
Right(new Cake(Poured(eggs, flour, sugar, butter, in.state)))
}
}
def mix(in: Cake[Poured], mixingTime: Int) = {
println("Mixing the ingredients...")
if(mixingTime <= 0) {
Left("Dont be lazy and mix the ingredients!")
}
else {
Right(new Cake(Mixed(mixingTime, in.state)))
}
}
def bake(in: Cake[Mixed], bakingTime: Int) = {
println("Baking the cake...")
if(bakingTime <= 0) {
Left("You want your cake raw?")
}
else {
Right(new Cake(Baked(bakingTime, in.state)))
}
}
}
object Demo extends App {
val myCake = for {
poured <- Cake.pour(Cake.start(), 3, 2, 2, 2).right
mixed <- Cake.mix(poured, 5).right
baked <- Cake.bake(mixed, 5).right
} yield baked
useCake(myCake)
val notBaked = for {
poured <- Cake.pour(Cake.start(), 3, 2, 2, 2).right
mixed <- Cake.mix(poured, 5).right
baked <- Cake.bake(mixed, 0).right
} yield baked
useCake(notBaked)
def useCake(maybeCake: Either[String, Cake[Baked]]) = maybeCake match {
case Left(message) => println(message)
case Right(cake) => eatCake(cake)
}
def eatCake(cake: Cake[Baked]) = {
println(s"I ate this very nice cake: ${cake}!")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment