Created
November 25, 2015 22:05
-
-
Save ruescasd/9e0fa196ab58f1fcb064 to your computer and use it in GitHub Desktop.
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
/* | |
* 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