Skip to content

Instantly share code, notes, and snippets.

Created October 16, 2013 03:29
Show Gist options
  • Save anonymous/7002273 to your computer and use it in GitHub Desktop.
Save anonymous/7002273 to your computer and use it in GitHub Desktop.
Functional coin toss (using hastily constructed partial abstractions)
sealed trait Command
case object Bid extends Command
case object Quit extends Command
case object Unrecognized extends Command
sealed trait Result
case object Won extends Result
case object Lost extends Result
sealed trait Face
case object Heads extends Face
case object Tails extends Face
case class Bet(amount: Int, on: Face)
case class Player(name: String, money: Int = 100)
object Program extends IOProgram {
def IOMain(): IO[Unit] = {
for (
_ <- IO { print("Your name? ") };
a <- IO { readLine };
_ <- IO { println("Hello, " + a) };
_ <- gameLoop(Player(name = a), rng(1, 2))
) yield {
}
}
def gameLoop(p: Player, rngs: Stream[Int]): IO[Unit] = {
for (
_ <- IO { println("Hey " + p.name + ", looks like you want to make a bet with that juicy $" + p.money + " you're waving around!") };
_ <- IO { print("Would you like to (B)id or (Q)uit? ") };
c <- IO { parseCommand(readLine) };
_ <- executeCommand(c, p, rngs)
) yield {
}
}
def executeCommand(c: Command, p: Player, rngs: Stream[Int]): IO[Unit] = c match {
case Bid => for (
b <- bidLoop;
_ <- announceResult(b, bet(b, rngs));
_ <- handleResult(b, bet(b, rngs), p, rngs)
) yield {
}
case Quit => IO.Unit
case Unrecognized => for (
_ <- IO { println("I didn't recognize that command.") };
_ <- gameLoop(p, rngs)
) yield {}
}
def handleResult(b: Bet, r: Result, p: Player, rngs: Stream[Int]): IO[Unit] = r match {
case Won => gameLoop(p.copy(money = p.money + b.amount), rngs.tail)
case Lost => gameLoop(p.copy(money = p.money - b.amount), rngs.tail)
}
def parseCommand(s: String): Command = s.toLowerCase.head match {
case 'b' => Bid
case 'q' => Quit
case _ => Unrecognized
}
def announceResult(b: Bet, r: Result): IO[Unit] = r match {
case Won => IO { println("You won $" + b.amount + "! Congratulations!\n") }
case Lost => IO { println("You lost $" + b.amount + "! Sucker!!!!\n") }
}
def bidLoop(): IO[Bet] = {
for (
a <- amountLoop;
f <- faceLoop
) yield {
Bet(a, f)
}
}
def amountLoop(): IO[Int] = {
for (
_ <- IO { print("Amount to bet? ") };
a <- IO { readLine }
) yield {
safeParseInt(a).getOrElse((IO { println("Invalid amount there buddy. Try again!") }).flatMap(x => amountLoop).unsafePerformIO)
}
}
def faceLoop(): IO[Face] = {
for (
_ <- IO { print("(H)eads or (T)ails? ") };
f <- IO { readLine }
) yield {
safeStringToFace(f).getOrElse((IO { println("Make a real call!") }).flatMap(x => faceLoop).unsafePerformIO)
}
}
def safeStringToFace(s: String): Option[Face] = s.toLowerCase.head match {
case 'h' => Some(Heads)
case 't' => Some(Tails)
case _ => None
}
def safeParseInt(s: String): Option[Int] = try {
Some(s.toInt)
} catch {
case _ => None
}
val r = new java.util.Random
def rng(min: Int, max: Int): Stream[Int] = Stream.cons(r.nextInt((max - min) + 1) + min, rng(min, max))
def toss(rngs: Stream[Int]): Face = if (rngs.head % 2 == 0) Heads else Tails
def bet(b: Bet, rngs: Stream[Int]): Result = if (b.on == toss(rngs)) Won else Lost
}
import scala.{Unit => U}
object IO {
def apply[A](f: => A): IO[A] = new IOMonad(U => f)
val Unit = apply(())
}
sealed trait IO[A] {
def unsafePerformIO: A
def map[B](f: A => B): IO[B]
def flatMap[B](f: A => IO[B]): IO[B]
}
private class IOMonad[A](action: U => A) extends IO[A] {
def unsafePerformIO: A = action()
def map[B](f: A => B): IO[B] = new IOMonad(f.compose(action))
def flatMap[B](f: A => IO[B]): IO[B] = new IOMonad(U => f.compose(action)().unsafePerformIO)
}
abstract class IOProgram {
def IOMain(): IO[U]
def main(args: Array[String]): U = IOMain.unsafePerformIO
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment